planner: /planner skill with WeveNova MCP + role management - #245
Open
Harsheet jain (jainharsheet77) wants to merge 51 commits into
Open
Conversation
…Step 1) Implements Step 1 of the ADK plan-generation dev spec: a local-first, structured Plan the /planner skill authors for an ESS rollout. - scripts/planner/: Plan model (atomic IO, validation, summary render, Flow-2 grouped-by-role discovery), TOC-first Microsoft Learn research selection, observe-mode output capture (/setup -> environmentId from config.json), an absent-safe roles-source seam, and a CLI the skill drives. - src/skills/planner/ + planner.prompt.md: the grounded interview -> Learn-grounded roles/tasks -> Flow-1 person assignment -> capture playbook, wired into copilot-instructions routing and the menu. Planner is the one experience allowed before setup (planning decides a greenfield deployment). - tests/planner/: 49 tests (pure logic + local IO; no network/cassettes). - dev-specs/adk-plan-generation/: the design doc. The local Plan is shaped like the WeveNova Plan/Task entities so a future sync is a field copy. WeveNova, tenant inventory, and the roles source are all optional, absent-safe seams; the Plan is authoritative on disk without them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture and expose scenario-to-scenario ordering (the PM spec's "HR knowledge before HR ticketing") without a new typed collection, consistent with the one-Context-bag model: - A scenario in scope is a Context entry (group "scenario"); a dependency edge is a Context entry (group "scenarioDependsOn", key "A -> B", scalar value = kind requires|recommends, description = rationale/PM-spec citation). - A grounded PM-spec seed (knowledge -> ticketing) lets the planner advise the sponsor; unmet_scenario_dependencies() surfaces prerequisites not in scope. - Exposed via the interview (check-deps) and rendered in summary with a met/MISSING status; ordering is then enforced by the task produces/consumes DAG. - New CLI: add-scenario, add-scenario-dependency, check-deps. 15 new tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ct onboarding framing - Routing: greenfield "set up ESS for the first time / where do I start / how do I get started" now routes to /planner (not /setup); the planner emits "run /setup" as the first task. Added the gate exception + trigger phrases. - Onboarding framing: /setup (onboarding) connects the kit to an ALREADY-deployed ESS agent and records its details - it does not create the environment or install ESS (those are portal/admin prerequisites on a new tenant). Reworded model.md, capture.md, planner.prompt.md, capture.py docstring, and the dev-spec. - Back-propagation documented: the details /setup records in .local/config.json (environmentId, dataverseEndpoint, agent slug/schema/folder) flow to later tasks via config.json (every skill reads it) and via the pinned primaryEnvironment artifact (tasks that consume it) - e.g. topic create. Docs/framing only; no logic change. 64 tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ut, stronger skill steps Addresses a real run that stopped after a single "run setup" task and skipped the system/scenario questions: - CLI: `task-brief` shows an assignee how to do a task, their role, the resolved values it consumes (e.g. the env id setup produced - the back-propagation), and the keys to capture. `pin-output` commits what an assignee created (Workday connection, Entra app, eval suite) onto the plan - the generic ask-mode counterpart to capture-setup. - Model: Plan.resolved_consumes() + Plan.task_brief(). - Skill instructions made prescriptive so the agent runs the WHOLE flow: the interview must capture which systems + scenarios (mandatory) before Phase 3; Phase 3 emits the full grounded task set (setup + one connect per system + authoring per scenario + evals + publish), not just setup; Phase 5 briefs each assignee with the env id and commits what they create back onto the plan. - 5 new tests (69 total). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…, check-deps, research extraction, summary timestamp Ground the planner on the PM spec model (scenarios come from the maker + Microsoft Learn; a business-scenario catalogue is an optional implementation choice) and fix four greenfield smoke-test bugs. Grounding: - Remove the invented scenario_catalogue.json + catalogue.py (a business-scenario list the PM spec does not define). - Add planner_facts.json + facts.py holding non-Learn facts ONLY: scenario dependencies (each with an explicit source) + a recognition lexicon. Not a scenario catalogue. - known_scenario_dependencies() now reads the facts file; no false "PM spec" citation. The knowledge->ticketing edge is sourced "ess-design-guidance" and flagged confirm-citation (verified: absent from pm-spec.md and the ADO spec repo). - Drop scenarios/suggest-scenarios CLI (prompt->fixed-list mapping). Bug fixes: - bug1: add-system CLI + Plan.set_system write scoped keys (system.<area>) so multiple target systems no longer collide on one reused key; interview.md asks per-area. - bug2: check-deps + Plan.scenario_dependency_status() surface MET dependencies, not only unmet. - bug3: research.extract_signals/strip_html/fetch_page_text + `research --extract` pull role/output candidates off fetched Learn pages. - bug4: plan carries updatedAt (bumped on save); summary renders Generated + Updated. Tests: 82 pass, 1 live skipped. Adds tests/planner/test_facts.py; updates scenario/research/cli/plan_model tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…connectors The interview asked "which backend systems should ESS connect to" with improvised examples (incl. ADP, which has NO native ESS connector). Ground the systems capture in Phase-1 Learn research instead of improvising: - Native ESS integrations = Workday, ServiceNow HRSD/ITSM, SAP SuccessFactors (each has an ESS Learn page in the TOC). Derive the set from research; don't name systems from memory. - SharePoint / M365 content is a knowledge source, not a data-system connector. - A system with no native connector (ADP, Jira, Dynamics 365, custom HTTP API) routes to /create (custom Power Automate flow), NOT a connect task. model.md Phase 3 now emits a create task (not a fabricated connect task) for non-native systems. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
If a plan already exists, /planner asked for the objective again because the
plan-existence check was buried at the bottom of SKILL.md (after the phases +
"the interview must capture the objective" emphasis).
- Promote plan lookup to the FIRST step ("First - resume or start"): if
workspace/plan/plan.json exists, show its latest state (summary) and the tasks
the person can pick up, role-gated (Flow 2), and do NOT re-run the interview or
re-ask the objective. Start over only on explicit confirmation.
- A task is shown only if the person holds the role it needs; role resolution is
best-effort until the roles source / MCP exists (future work).
- Frame the phases as "building a new / extending a plan"; reinforce the resume
gate in planner.prompt.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ironment /setup should be deliberate and plan-driven, not auto-fired by the gate. Drive it from plan state: - The Power Platform admin's setup task decides/creates the environment and pins primaryEnvironment. Every OTHER persona must connect their own kit to that same environment before their task's skill works. - Plan.kit_setup_nudge(): for a non-setup kit-skill task, once primaryEnvironment is pinned, returns the env id/url to connect to; None for the setup task itself, non-kit tasks, or when no env is pinned yet (then the setup task is the prerequisite, not a nudge). - task_brief surfaces it; the task-brief CLI prints "First connect your kit: run /setup and choose environment <envId>". - Skill docs (mytasks/capture/model/SKILL) explain the plan-driven nudge. Tests: 83 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The gate's welcome told every user to "Type /setup", which is a VS Code prompt-file command that does not exist in the Copilot CLI. Reword to name both surfaces: type /setup in VS Code, or just say "set up ESS" in the CLI/any chat. The routing exceptions already treat "explicitly asked to run setup" the same as /setup, so natural-language invocation works. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Ship the kit's CLI entry points inside the repo so anyone who clones it gets them
automatically (no per-user ~/.copilot copy or sync needed). The Copilot CLI
auto-discovers project skills from .github/skills/<name>/SKILL.md.
- solutions/ess-maker-skills/.github/skills/setup/SKILL.md
- solutions/ess-maker-skills/.github/skills/planner/SKILL.md
Names match the VS Code prompt-file commands (/setup, /planner) so the skill name
is consistent across Copilot in VS Code and the CLI. Each is a thin launcher that
honors the kit's copilot-instructions then follows the real
src/skills/{onboarding,planner}/SKILL.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Tasks are assigned a Learn-grounded role at creation (the role that should be able to pick the task). Make that grounding explicit and auditable: - new_task / add-task gain --role-source: the Microsoft Learn URL that grounds the task's role (optional task.roleSource field). task_brief surfaces it as "Role grounded in: <url>". - research.md: `research --extract` surfaces role candidates per page; carry the (role, source URL) pair into task creation. - model.md: every task gets a Learn-sourced --role + --role-source at creation; the person is assigned later (Phase 4 / future external roles API). - assign.md + dev-spec: document the future external roles API — RoleSource .list_holders resolves role->person, and a task can be assigned a user together with the role. The role stays Learn-grounded; the API only resolves people. Tests: 85 pass (adds role-source coverage). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d it When /setup finishes it now nudges the maker to record it on the rollout plan instead of waiting for someone to notice. - onboarding step3-flightcheck.md gains a plan-aware handoff (3.4): at the end of setup (readiness check skipped or done), if workspace/plan/plan.json exists, offer to mark the setup task complete and pin the environment, running `capture-setup --complete`. Plan-conditional, so standalone /setup users are unaffected. - Plan.setup_task_id() finds the plan's /setup task (the onboarding-skill task, not a portal "provision" task). `capture-setup --task` is now optional and auto-detects it, so the handoff is turnkey. - capture.md documents the auto-detect and the automatic offer. Tests: 87 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…description The WeveNova Task entity has no `action` field, so emitting one would be rejected on persist. Remove it entirely — a Task is described by title + description (the description states the "how": which command to run, or a portal/manual step). - plan_model: drop the action_* builders, ACTION_KINDS/ONBOARDING_SKILLS, the `action` param/field on new_task, and _validate_action. Setup detection is keyed on the grounded produces/consumes signal (the task that produces primaryEnvironment), not action. Summary tasks table drops the Action column; task_brief returns `description`. - cli: remove _build_action and the --skill/--action-kind/--ref flags; add-task is title + description (+ role / role-source / produces / consumes). - skill docs (model/capture/mytasks/research/SKILL): title + description, no action. - dev-spec: remove task.action from the schema, JSON examples, and §9/§10/§12/ appendix; state the WeveNova Task entity has no action field. - tests rewritten to description-based tasks; 87 pass, no action references remain. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tep-2 entities
Audited every field against the Step-2 "Plan Enrichment & Persistence" dev spec
(the WeveNova AgentConfigurationPlan/Task entities), removing invented fields and
correcting spec-backed ones.
Removed (not in the WeveNova entity):
- task.roleSource - the role's Learn grounding lives in the research context
(Step-2 §7.6 prerequisites[].sourceUrl), never as a task field.
- plan.notes - §7.1 defines only Context + Outputs; free-form notes are Context
entries, not a top-level field.
- plan.generatedAt / plan.updatedAt - the entity timestamps are server-owned
(CreatedAt/UpdatedAt, §7.5); the local pre-sync file no longer sets them (summary
drops the Generated/Updated line).
Kept / corrected (confirmed present in Step-2):
- Principal.Role {roleId, directoryRef?} and Principal.User {oid, directoryRef?}
(§7.4) - RESTORED the optional directoryRef I had wrongly removed.
- task.produces / task.consumes (IList<string> keys, §7.2); setup detection via
produces primaryEnvironment.
Result: plan = {schemaVersion, planId, projectId, status, context, tasks, outputs};
task = {id, title, description, assignedTo, state, produces, consumes}. Docs
(model/research/dev-spec) updated. 86 tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…o context)
A real run went system-first ("Which back-end system?" -> Workday) and then
reduced scenarios to that one system's capabilities (profile/time-off/pay),
never asking which scenario TYPES the maker wanted (HR knowledge, HR ticketing,
IT ticketing). Reorder the interview so it builds the scenario context first:
- Q2 now captures scenarios / jobs-to-be-done FIRST, prompting with the grounded
HR knowledge / HR ticketing / IT ticketing / data-actions framing (examples,
not a fixed catalogue).
- Q3 asks the system PER scenario, only after scenarios are captured.
- Explicit rule: picking a system must not narrow the scenario set; a maker on
Workday may still want HR knowledge and IT ticketing. "Required before Phase 3"
reordered: objective -> scenarios -> system-per-scenario.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…alogue Add the authoritative ESS scenario catalogue (scenario list, priority order, and dependency edges) as vendored data, and wire the interview to it so a sponsor's goal is captured against real categories instead of ad-hoc examples. - scripts/planner/scenario_catalogue.md: vendored decision-layer snapshot - the category map (6 OOB categories / 43 scenarios + extensible E1-E7/Facilities), the default priority order + tiers, and the dependency edges. Per-scenario detail (fields, setup, connectors, roles) stays fetched from Microsoft Learn at render time. No internal repo path/source is recorded in the file. - interview.md: Phase 2 reads the catalogue; Q2 maps the sponsor's goal to the catalogue categories (HR Knowledge, HR/IT Ticketing, Profile read/write, Manager, Handoff, extensible); priority/order and dependency edges come from the catalogue, not improvised. - planner_facts.json: dependency edges now mirror the catalogue (Knowledge is the deflection foundation -> Ticketing recommends Knowledge; IT likewise; reads before writes), each sourced "ESS scenario catalogue". Replaces the earlier single unsourced edge. - tests updated (kind recommends; catalogue source). 86 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d eval after plan creation After the plan is authored (Phases 1-4), the planner now hands the sponsor's captured scenarios to the eval skill to generate a first, theoretical (scenario-based) evaluation - generate-only, before anything is built. The planner only invokes the eval skill; it does not own or author eval content. Adds Phase 5 (evaluate.md), renumbers Capture to Phase 6, and documents the seed-vs-refine relationship to the topic-driven 'Generate evaluation tests' task (unchanged). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… briefs, per-category enabled-scenario capture Rename the plan's human view to ESS-scenario-plan.md and make it editable: a Plan editor revises it directly (or via chat intent) and the planner reconciles the change back into plan.json via the CLI, asking where ambiguous (new src/skills/planner/edit.md). Adds update-task and remove-task CLI/model verbs so reconciliation can modify/delete tasks. Enrich the task brief on start: when an assignee engages a task, render a detailed how-to - hand off to the owning kit skill (/setup, /connect, /create, /evaluate) or fetch the step's Learn page for portal/manual steps (register Entra app, provision env, publish). Mantra: enrich from Learn; descriptions carry the how, detailed steps are fetched fresh from the task's Learn anchor. Capture the enabled scenarios per in-scope category (Context group scenarioCapability, grounded from the catalogue named list + Learn, OOB unless the editor pins an extensible one) so the theoretical eval reads topic-level scenarios off the plan and writes golden prompts per scenario. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… via setup
Q8 ("brand-new environment, or do you already have ESS running?") selected a
greenfield-vs-enrichment branch, but enrichment is a future seam-only path
(section 14) with no implementation, and the greenfield-vs-existing distinction
is already resolved by detection at execution time: the always-emitted setup
prerequisites are idempotent (/setup reads .local/config.json; the install
pre-check reports an already-installed ESS as PASSED), so an existing deployment
no-ops satisfied Tasks without asking. Removing Q8 also honors the interview's
"fewest questions / propose, don't interrogate" rule.
Remove Q8 from interview.md and design section 8.2, fix the 4-8 / "8 is one
branch" counters, and record the resolution in open-items section 17.9. Q8
stored no context key, so nothing downstream is orphaned.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…thing) Bring the demo's early-eval idea onto the dev branch as a render-only preview: as soon as the interview captures scenarios + goals (Phase 2, before modelling), the planner renders the golden prompts grouped by scenario category so the sponsor sees the acceptance bar up front. Render-only: it displays the prompts in chat but writes no file, creates no eval records, and pushes nothing. The eval skill (evaluations/create/SKILL.md) is left untouched - the planner does not own it and does not run its generate/scan/push pipeline. Actual generation stays with the topic-driven Generate evaluation tests task, later. Reframes evaluate.md (Phase 5) from theoretical generate-only to eager render-only preview; adds the eager hook in interview.md; updates SKILL.md phase labels and the dev-design (10.2, worked example). Instruction-only; 89 tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gram Add the end-to-end flow diagram (Mermaid, sec 3.1): sponsor + task-assignee lanes, the plan as shared state, the WeveNova tenant inventory box, and the numbered info-transfer arms including /setup WRITE, WeveNova READ (value/presence-only), and Dataverse live READ (200/403). Align the spec to the flow: research now also scans the tenant inventory (7.8); capture (12) scans the WeveNova tenant inventory + controlled Dataverse APIs first (field visibility = value if safe-for-all else presence-only; access enforced by Dataverse 200/403, not the ADK), falling back to local observe (config.json) then ask; /setup artifacts persist to the tenant inventory and are read back from WeveNova (12.2). Reframe sec 14 from 'inventory is future' to a first-class absent-safe read/write seam (proactive /discover skip-existing stays future), and reconcile the sec 1 summary + sec 2 non-goals + sec 13 seam bullet. Docs-only. The tagged .docx in Downloads is a stale export (baseline 72a24f8); this repo .md is the living spec on PR microsoft#220. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nt->include, empty->leave) Clarify that research reads the tenant inventory in WeveNova (not Dataverse directly): if present, include it (detect existing deployment, pre-fill); if empty/absent, leave it and run Learn-only. Updates the sec 1 summary, sec 7.8, and the sec 3.1 flow-diagram research node. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tinct from the Dataverse APIs Correct the conflation throughout: the tenant inventory is a WeveNova artifact (we are implementing) that STORES facts about the tenant; it is DISTINCT from the Dataverse APIs those facts are fetched from. The planner reads it as one source alongside Learn links and Dataverse (if available). Flow diagram (sec 3.1): Dataverse now feeds the inventory (facts fetched from Dataverse 200/403 -> stored), and the planner READS the inventory (value if safe, else presence-only) rather than reading Dataverse directly. Reframes sec 1, sec 7.8 (the inventory is another source), sec 12.1/12.2/12.3, and sec 14 accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Nova does not fetch from Dataverse) Correct the capture path: WeveNova does NOT reach into Dataverse. The assignee runs /discover, which fetches the tenant facts from the distinct Dataverse APIs (200/403, the caller's own access) and then calls WeveNova to store them in the tenant inventory; the planner reads the inventory (value if safe, else presence-only). Rewire the sec 3.1 flow diagram (Dataverse -> /discover -> WeveNova inventory -> planner) and update the info-transfer arrows, sec 12.1/12.2/12.3, and sec 14 (the /discover run that POPULATES the inventory is part of this flow; only proactive skip-existing pruning is future). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…elds, permission) Add a read edge from the sponsor's Research to the WeveNova tenant inventory (we read it during planning, present->include/empty->leave), and pose the open questions in the diagram: which fields does the inventory expose to the planner, and what permission (if any) is required to read them. Also recorded as sec 18 open question 12. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…es) now, WeveNova MCP later Reflect the envisioned interim model: /discover crawls Dataverse for system details and writes ids+names into .local/config.json (same local file /setup writes); the planner reads ids+names from config.json for now and pins them to the Plan as a task output/PlanArtifact. /discover separately owns persisting to the WeveNova inventory and defining its read surface; the read moves to a WeveNova MCP once that stabilises. This matches capture.py, which reads environmentId from .local/config.json today. Rewire the sec 3.1 diagram (Dataverse -> /discover -> config.json -> planner; dashed /discover->inventory and inventory->planner-via-MCP), add a config.json node, and add a CAVEAT that the inventory needs a read surface exposing ids+names (which fields, what permission). Updated info-transfer, sec 7.8, sec 12.1/12.2/12.3, sec 14, and sec 18 microsoft#12. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…role tasks Gap 1: /setup clones the deployed ESS agent into config.json; capture now pins it as an Agent PlanArtifact alongside the Environment. Adds capture.detect_agent, extends config_snapshot with the agent, wires cmd_capture_setup to pin both, adds Agent to ARTIFACT_KINDS, and updates capture.md + design doc SS12. Gap 2: Workday setup is multi-role (App/Cloud App Admin SSO, Workday Administrator tenant, Environment Maker pack+connect, InfoSec/IT firewall) per setup/workday/tasks.md - it is never one integration-owner task. Rewrites model.md 'a task is not a skill steps' to split on every role boundary and read the checklist role: verbatim; updates the backbone tables + worked plan.json + walkthrough in the design doc to match. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…act), not env/agent-only Replaces the env-only + agent-only detectors with a single generic capture.detect_config_artifacts: it diffs the whole .local/config.json and pins EVERY id+name (and any other artifact a skill recorded) as a PlanArtifact - the environment and cloned agent get recognised kinds/keys (Environment, Agent), and any other id-bearing object or list of objects is captured too (Connection, EntraApp, KnowledgeSource, or Custom). config_snapshot now returns the full config (deep copy) so the sweep sees every key; cmd_capture-setup pins all detected artifacts. Updates capture.md, model.md, and the design doc SS12/SS3.1 to frame capture as generic. Adds detect_config_artifacts tests (env+agent, generic connection/custom, list-of-objects, changed-only, empty). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nts, validated saves Code correctness: - capture: generic config sweep now requires a real before-snapshot (add snapshot-config + capture-setup --before-file); with no snapshot only the recognised env+agent are pinned so pre-existing config is never mis-attributed. Never sweep the 'agents' inventory list (no duplicate/historical agents). Add capture-setup --dry-run (preview before pinning). - plan_model.claim_task rejects tasks that aren't an open role pool (never silently replace an owner / erase a role). - tasks_for_person (Flow 2) excludes Completed tasks. - render_summary shows met AND unmet scenario dependencies (scenario_dependency_status). - validate flags artifacts whose producedByTaskId references an unknown task. - cli._save validates before persisting (refuse to write an invalid plan). - pin-output/capture-setup: reject malformed --attr; do not mark a task Completed while declared produces are unresolved (pin still persists). - summary is read-only (no longer rewrites ESS-scenario-plan.md, so it can't clobber unreconciled edits). Docs: - register /planner in the repo-root wrong-folder redirect list. - SKILL.md: /setup connects to an already-deployed env (does not create it). - capture.md: planner invokes capture after /setup (setup flow has no hook). - __init__/plan_model docstrings: RoleDirectory (not IRoleDirectory); no task 'action' field. - edit.md: narrow the editable surface (description/produces/consumes are chat-intent, not table columns). - research.md: research context is session notes today (no persisted sidecar); briefs re-read Learn live. - scenario_catalogue.md: note the Handoff any-of prerequisite is a known check-deps limitation. - fix add-task usage example (--skill removed). Tests: +9 planner tests (claim rejection, Flow-2 completed exclusion, unresolved-produces, orphan-artifact validation, dry-run, read-only summary, attr validation, completion guard). 108 pass; ruff clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… of the flow HTML) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….md) Matches the dev-specs convention used by eval-driven-topic-maker (a concise <feature>-dev-plan.md with Objective / MVP scope / Technical approach / status + work-plan + backlog tables / Ownership / Dependencies / Completion criteria / Key risks). The detailed technical design remains alongside it as a companion reference; README now points at the dev plan first. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…table role ids, command-neutral redirect
- plan_model.set_task_state now enforces the unresolved-produces invariant for EVERY caller: it refuses to mark a task Completed while a declared produces key has no Active artifact (set-state --state Completed was bypassing the CLI-only guard). cmd_set_state surfaces it cleanly.
- roles: add slugify_role_id(label) -> stable kebab id; document that a role id is a well-formed handle and the checklist's verbatim role: is the DISPLAY NAME. model.md + design doc now use stable ids (app-cloud-app-admin, workday-administrator, environment-maker, infosec-it) for the Workday decomposition, keeping the human label as the display — so documented plans pass the role-validation seam.
- repo-root wrong-folder redirect: make step 5 command-neutral ('type your command again, e.g. /setup or /planner') so a /planner request isn't converted into /setup after reopening the folder.
- Tests: set-state Completed invariant (model + CLI), slugify_role_id round-trip. 110 pass; ruff clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an optional WeveNova-backed persistence seam so the planner can get/persist the project plan and its tasks over the weve-plan MCP server instead of local plan.json, while still rendering ESS-scenario-plan.md locally.
New modules:
- mcp_client.py: a stdlib (urllib) Streamable-HTTP MCP client (initialize, tools/list, tools/call; JSON + SSE; Mcp-Session-Id). Endpoint from .vscode/mcp.json (weve-plan) with PLANNER_MCP_URL/HEADERS overrides. python -m planner.mcp_client --ping.
- weve_mapping.py: pure bidirectional mapping between the local camelCase model and the WeveNova PascalCase entities. Grounded in a live get_project_plan + get task response (fixtures). Handles the real shapes: Context/Outputs with Attributes as [{Key,Value,...}], and task assignment as flat AssignedToId/AssignedToRoleId scalars (not a nested object). AcceptanceCriteria folds into the acceptanceCriteria context group.
- plan_store.py: LocalPlanStore (plan.json) + McpPlanStore (WeveNova). McpPlanStore.load reads plan+tasks (degrades to plan-only with a warning if the tasks collection is unavailable); save reconciles tasks (create/update/delete, skipping unchanged) and re-renders the .md. Plan-level context/outputs are read-only over the current MCP surface (surfaced as a notice).
CLI: global --store {local,mcp} (default local; PLANNER_STORE env). _load/_save go through the store; init is guarded in mcp mode (never wipes the upstream plan). The .md view is written either way.
Verified live against the weve-plan dev tunnel: validate -> 'Plan is valid'; summary renders the real WeveNova plan context/outputs + tasks with correct assignment mapping. Task writes use the correct scalar body; live writes on the demo plan are blocked only by its Completed status (upstream invariant), surfaced as a clean error.
Tests: +2 files (mapping against real fixtures; store CRUD against an in-memory fake) with an opt-in live smoke. 124 planner tests pass; ruff clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ull (--store mcp) Per the target model, once the weve-plan MCP is configured the plan lives in WeveNova: - WeveNova is authoritative. McpPlanStore.load always FETCHES the plan+tasks from WeveNova (never the local file), and save reconciles tasks then renders ESS-scenario-plan.md from the RE-FETCHED WeveNova state (so the .md is generated from WeveNova, including server-assigned TaskIds). - Local plan.json is written only as a cache/mirror (cache_path; make_store mcp_cache=True) — never read as truth. - New 'pull' CLI verb: fetch the plan for the project/agent being configured from the active store (WeveNova with --store mcp) and materialize the local .md view. This is the resume entry point for a WeveNova-backed agent. - SKILL.md 'First — resume or start' now checks WeveNova first when the weve-plan MCP is configured (or PLANNER_STORE=mcp): run 'pull' to fetch, resume if a plan exists, and use --store mcp on subsequent reads/writes. Intro + README document WeveNova-as-source-of-truth, the local cache, and md-generated-from-WeveNova. Verified live: 'pull' fetches the WeveNova plan (2 tasks, 1 output), writes the plan.json cache + generates the .md from WeveNova. 126 tests pass (added cache-write + md-from-weve coverage); ruff clean. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…role-aware tasks) Wire the planner to the weve-open-mcp 3.1.0 tool surface and add role management per the role-integration spec. - roles.py: add the WeveNova role registry (internal authority + attestable External/Entra/PowerPlatform roles) with ordinal, case-sensitive ids emitted verbatim (no slugify/lowercase); find()/from_mcp() resolve display->canonical. - weve_mapping.py: emit AssignedToType/AssignedToId/AssignedToRoleId; read back via expanded AssignedTo, scalar type, then pooled inference. - plan_store.py: rebind McpPlanStore to the multi-plan 3.x surface (project_id/ plan_id/tenant_id); thread the required If-Match etag through update/set-state/ delete and re-read a fresh etag between chained mutations; resolve_plan_binding. - attest.py (new): AttestationClient (attest/list/get/revoke/caller-tasks) with local validation mirroring the server ValidateAttestationRequest. - cli.py: new roles/attest/assignments/revoke/caller-tasks commands, global --project-id/--plan-id, soft role canonicalization. - skills docs (SKILL/model/assign/mytasks/interview): teach verbatim role ids, attestation (Phase 4) and server-resolved Flow 2 (caller-tasks). - tests: registry, mapping, store (with etag-enforcing fake) and attest coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…out-of-box - New standalone /roles skill (SKILL.md, roles.prompt.md, roles_cli.py) decoupled from /planner for attestation, role validation, and role-aware task routing. - Add role-holder reverse-lookup guidance (Work IQ/Graph discovery -> attest flow) since WeveNova is attest-only and cannot enumerate role holders. - Ship Work IQ MCP out-of-box via committed .mcp.json (static local stdio server, no secrets, zero /setup). - Trim role logic out of /planner (cli.py, SKILL/model/assign/mytasks.md); wire /roles into menu + README. - Add tests/planner/test_roles_cli.py (4 tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- create_project_plan sends {projectId, plan} to match live weve-open-mcp v4.1.0 (objective seeded as a Context entry so it round-trips).
- add 'push': author locally, reconcile the whole plan to WeveNova in one pass (--force to reuse an existing plan); avoids one server write per interview field.
- add 'find-users' (roles_cli): resolve a person name -> aadId via WeveNova; roles SKILL.md instructs assigning a Power Platform role to a named user via this lookup.
- planner SKILL.md: document the author-locally-then-push-once workflow.
- mcp_client ping + weve_mapping fixes; setup-gate rewrite in copilot-instructions.
- tests: new test_mcp_client.py; updates across planner suite (181 passed).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…asks flow) Make the WeveNova resume pull unconditional instead of gated on the persona deciding the weve-plan backend is configured; run --store mcp pull and branch on the result (plan -> resume; empty -> create+push; unreachable/unconfigured error -> only then fall back to local). Add a guard so the planner never concludes there is no plan / starts interviewing until the pull has actually run. mytasks.md (Flow 2): add missing Step 0 to fetch the live plan from WeveNova before resolving roles, so the assignment flow cannot report nothing-assigned without hitting WeveNova. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove all Work IQ references from role-resolution instructions and rewire name-to-OID lookup around the WeveNova people tools (find_users_by_name / get_user_by_aad_id / list_cached_users) exposed on the same weve-plan MCP the plan already uses. Work IQ WAM auth is blocked on this build (issue microsoft#92), so it is dropped for now. Rewrites the /roles SKILL and prompt, the planner hand-off notes (model/assign/mytasks/SKILL), and the roles CLI docstrings/help. Reverse-lookup is now honest about WeveNova not enumerating tenant-wide holders: it lists the plan roster and hands tenant discovery back to the maker. Also drops the now-empty .mcp.json (its only server was workiq). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…only) - caller-tasks resolves the caller from --caller or PLANNER_MCP_CALLER_ID; it must be the tunnel-authenticated user's own OID. WeveNova reads callerId as a self-scope sentinel (assignedToId eq '<callerId>') and only then expands the caller's attested roles into pooled tasks -- so a looked-up person (e.g. 'primary') won't work. Reject a non-GUID caller up front. - tasks_for_caller: send the OData query as an options object (query.filter), not a bare string, per the list_project_plan_tasks_for_caller schema; add an optional passthrough --filter (caller scope stays implicit). - docs: roles/planner SKILL.md + mytasks.md now state my-tasks is self-only and never uses a find-users result as the caller. - tests: caller defaulting from env, GUID guard, and query-object shape (187 passed). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…filter, let WeveNova expand Reframe Flow 2 (mytasks.md) so caller-tasks is the primary path: agents pass only the authenticated caller's own OID and WeveNova expands their attested roles server-side. Remove the 'find the person's roles' lead step and demote 'mine --roles' to an offline-only fallback. Reinforce in roles/SKILL.md that there is no role API to enumerate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Nova 4.3.0) Swap the removed find-users name->OID directory lookup for the new current-user command backed by WeveNova 4.3.0's get_current_user_context tool. caller-tasks now auto-resolves the authenticated caller (precedence: --caller > PLANNER_MCP_CALLER_ID > get_current_user_context), so ADK never asks the person for their AAD id for self-scoped 'what are my tasks' queries. Updates roles/planner skills, prompts, and the CLI test suite (replaces find-users tests with current-user + auto-resolve coverage). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WeveNova dropped get_current_user_context. The ADK now reads the authenticated userName + aadId from the kit .env (load_adk_identity, with PLANNER_MCP_USER_NAME/PLANNER_MCP_AAD_ID overrides) and McpClient injects them into every tools/call so the server always knows the caller. 'What are my tasks' (caller-tasks) auto-resolves the self-scope from the .env aadId (precedence: --caller > PLANNER_MCP_CALLER_ID > .env), so ADK never prompts for an AAD id. Updates roles/planner skills + prompt; replaces the removed current-user tests with .env identity and per-call injection coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
WeveNova exposes find_users_by_name (find cached demo users by display name/alias/email and return their AAD IDs) as a temporary stand-in for Work IQ people search. Restore the roles-CLI find-users command (+ _weve_client/parse helpers) so 'assign <role> to <name>' resolves the named person's aadId, which attest --person then binds. The .env caller stays the self identity for attesting-as-me and 'what am I assigned?' (Flow 2). Updates /roles skill + prompt to wire the assign-by-name flow and mark it Work IQ-temporary; restores the find-users test coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ull-plan dump Flow 2 (what am I assigned?) was pulling the whole plan and then filtering client-side, and when nothing matched the caller it enumerated every task in the plan with who each was waiting on. That ignored the server-side self-scope filter and leaked the entire plan. The scoped WeveNova call (caller-tasks -> list_project_plan_tasks_for_caller with callerId) already returns exactly the right result, including a clean No tasks waiting on you when the caller holds no roles. This makes the docs enforce that: caller-tasks output is the whole answer; step 0 pull is existence/context only and must never be read out as the task list; never filter the plan client-side; on an empty scoped result, say nothing is assigned and stop rather than dumping the plan. Same guardrail added to the roles skill Flow 2. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…le hardening Render a role's / caller's tasks in dependency order: a stable topological sort over produces->consumes edges, falling back to authoring (Learn) order when two tasks are independent. Wired into render_summary, the `mine` command, and `caller-tasks` so "what are my tasks" lists prerequisites first (e.g. Run setup before Check environment readiness). Also aligns the planner test suite with the in-progress WeveNova lifecycle + ETag-safety hardening (activate-before-state-change, direct-read ETag before mutate, schema-checked identity injection, post-attest verification readback, Blocked->Cancelled) and adds the MCP integration test tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Encode the single-role vs multi-role decomposition rule in the Phase-3 task model as the converse of the existing "split on every role boundary" guidance: when every step a kit skill runs is gated to the same role, emit one "run /<skill>" task for that role (Learn-sourced description + skill reference) rather than splitting its internal steps into same-role tasks. Also fix the "First step" section, which incorrectly claimed /setup does not create the environment and told planners to add a separate provision task ahead of it. /setup verifies-or-creates the Power Platform environment as its own first step (setup/workday/provision-power-platform-environment.md), so the Power Platform Administrator's provisioning + onboarding is the single "run /setup" task, not a redundant standalone "Provision" task. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After a verified attestation, roles attest now reassigns every task still in that role open pool to the person (patched to User, grounding role retained), so the pool does not sit unowned and their caller-tasks lists it immediately. Never displaces a task already owned by someone; --no-assign-tasks opts out. Reuses direct-GET ETag If-Match with a single retry on conflict. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ject_plan_task (MCP 5.6.0) Completing a task that produced outputs now finishes it through the new complete_project_plan_task tool, transitioning NotStarted to InProgress first, then persisting ALL the task Active outputs in a single bulk call (not one per output) - WeveNova records outputs only at completion. Adds output_to_completion (camelCase, kind clamped to the four-value enum, EntraApp/Agent fold to Custom) and Plan.completion_outputs; save re-fetches and re-renders. Also fixes update_project_plan_task PATCH to send only the schema-allowed fields (additionalProperties false): claiming a pooled task sets ONLY AssignedToId, so attest assignment and task sync no longer send server-derived AssignedToType/AssignedToRoleId. Validated the completion payload against the live 5.6.0 tool schema. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…capture persists to WeveNova setup.py write_config now reads the locked environment identity from setup state (.local/setup/config.json) and stamps environmentId + environmentName into .local/config.json; config.json recorded only the org URL before. capture.detect_environment backfills the GUID from setup state for configs written before the stamp, so the primaryEnvironment artifact carries a non-empty inventoryRef. Without the id WeveNova rejected the Environment output when completing the Run setup task. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the
/plannerskill to the ESS Maker Kit and integrates it with the WeveNova plan MCP server (weve-open-mcp3.1.0), including full role management (role registry, plan-scoped attestation, role-aware/caller task filtering, write-time role validation).The planner authors a structured Plan for an ESS rollout — grounded on Microsoft Learn, interviewed for the rest — as atomic Tasks each owned by a role and a person, with a ledger of what each Task produced. The plan persists locally or in WeveNova via
--store mcp.What's in this PR
src/skills/planner/*): research → interview → model → assign → evaluate(preview) → capture phases, plus Flow 2 ("what am I assigned?").scripts/planner/plan_store.py,mcp_client.py): reads/writes the WeveNova project plan; bound to the multi-plan 3.x tool surface (project/plan/tenant ids); threads the requiredIf-Matchetag through update/set-state/delete and re-reads a fresh etag between chained mutations.roles.py— WeveNova role registry (internal authority + attestable External/Entra/PowerPlatform roles). Ids are emitted verbatim (ordinal, case-sensitive; no slugify/lowercase) to match the backend exactly.weve_mapping.py—AssignedToType/AssignedToId/AssignedToRoleIdtask shape.attest.py—AttestationClient(attest / list / get / revoke / caller-tasks) with local validation mirroring the server'sValidateAttestationRequest.cli.py—roles,attest,assignments,revoke,caller-taskscommands + global--project-id/--plan-id.SKILL/model/assign/mytasks/interview): teach verbatim role ids, attestation (Phase 4) and server-resolved Flow 2.Role wire format (critical)
Role ids are matched ordinally and case-sensitively by the backend with no normalization — so the skill emits them exactly:
AgentOwner,AgentEditor,AgentAnnotator,AgentViewerWorkdayAdmin,ServiceNowAdmin,ServiceNowKnowledgeManagerGlobal Administrator,Network Administrator,User Administrator,Power Platform Administrator,Environment MakerTesting
python -m pytest tests/planner -q→ 150 passed, 2 skipped (the 2 are live-network tests). Store tests use an etag-enforcing fake client to validate the If-Match wiring. No live-server mutations were performed.