feat(skills): add skill-context command for MAXSIM state access#17
feat(skills): add skill-context command for MAXSIM state access#17maystudios wants to merge 1 commit intomainfrom
Conversation
Add a new `skill-context` CLI command that skills can invoke to get MAXSIM state including current phase, STATE.md fields, blockers, decisions, artifact paths, and config. Returns minimal context with null fields when .planning/ doesn't exist. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new CLI command intended to provide skills with a single JSON payload describing the current MAXSIM project state (phase, STATE.md-derived fields, decisions/blockers, artifact paths, and config), with a graceful null-field fallback when .planning/ is absent.
Changes:
- Introduces
skill-contextcommand implementation and exports it through the CLI/core entrypoints. - Updates built CLI distribution artifacts to include the new command.
- Updates built dashboard client asset outputs (hashes/CSS bundle replacement).
Reviewed changes
Copilot reviewed 3 out of 27 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/dashboard/dist/client/index.html | Updates built asset references to new hashed JS/CSS bundles. |
| packages/dashboard/dist/client/assets/index-DzJChB-D.css | Removes prior hashed CSS bundle (build artifact churn). |
| packages/dashboard/dist/client/assets/index-CxFKStBk.css | Adds new hashed CSS bundle (build artifact churn). |
| packages/cli/src/core/skill-context.ts | Implements cmdSkillContext() and the SkillContextResult JSON shape. |
| packages/cli/src/core/index.ts | Re-exports cmdSkillContext and SkillContextResult from core index. |
| packages/cli/src/cli.ts | Registers skill-context as a top-level CLI command. |
| packages/cli/dist/mcp-server.cjs | Rebuilt dist output (source map region path changes). |
| packages/cli/dist/install.cjs | Rebuilt dist output (source map region path changes). |
| packages/cli/dist/core/skill-context.js.map | Adds sourcemap for new skill-context build output. |
| packages/cli/dist/core/skill-context.js | Adds compiled JS for skill-context. |
| packages/cli/dist/core/skill-context.d.ts.map | Adds TS declaration sourcemap for skill-context. |
| packages/cli/dist/core/skill-context.d.ts | Adds TS declarations for skill-context. |
| packages/cli/dist/core/index.js.map | Updates core index sourcemap to include skill-context exports. |
| packages/cli/dist/core/index.js | Updates compiled core index to export cmdSkillContext. |
| packages/cli/dist/core/index.d.ts.map | Updates declaration sourcemap to include skill-context exports. |
| packages/cli/dist/core/index.d.ts | Updates declarations to include skill-context exports. |
| packages/cli/dist/cli.js | Updates compiled CLI to register skill-context. |
| packages/cli/dist/cli.cjs | Updates compiled CLI bundle to include skill-context implementation/registration. |
| packages/cli/dist/assets/CHANGELOG.md | Updates packaged changelog content (dist artifact). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| decisions: [], | ||
| artifacts: { plan: null, summary: null, research: null, context: null, verification: null }, | ||
| config: { model_profile: 'balanced', commit_docs: true, branching_strategy: 'none' }, | ||
| }; |
There was a problem hiding this comment.
When .planning/ is missing, the command hard-codes default config values. This duplicates the canonical defaults in PLANNING_CONFIG_DEFAULTS and can drift over time; consider reusing the shared defaults (or a helper) so skill-context stays consistent with the rest of the CLI.
| const files = fs.readdirSync(absPhaseDir); | ||
| for (const f of files) { | ||
| if (isPlanFile(f)) { | ||
| artifacts.plan = path.join(phaseDir, f); | ||
| } else if (isSummaryFile(f)) { | ||
| artifacts.summary = path.join(phaseDir, f); | ||
| } else if (f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md') { | ||
| artifacts.research = path.join(phaseDir, f); | ||
| } else if (f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md') { | ||
| artifacts.context = path.join(phaseDir, f); | ||
| } else if (f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md') { |
There was a problem hiding this comment.
Artifact detection currently iterates readdirSync() results and overwrites artifacts.plan/artifacts.summary for every matching file. In phases with multiple *-PLAN.md/*-SUMMARY.md files, this will return an arbitrary (filesystem-order) plan/summary and may not match **Current Plan:** from STATE.md. Consider selecting deterministically (e.g., sort and pick the plan matching Current Plan, or return lists instead of a single path).
| const files = fs.readdirSync(absPhaseDir); | |
| for (const f of files) { | |
| if (isPlanFile(f)) { | |
| artifacts.plan = path.join(phaseDir, f); | |
| } else if (isSummaryFile(f)) { | |
| artifacts.summary = path.join(phaseDir, f); | |
| } else if (f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md') { | |
| artifacts.research = path.join(phaseDir, f); | |
| } else if (f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md') { | |
| artifacts.context = path.join(phaseDir, f); | |
| } else if (f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md') { | |
| const files = fs.readdirSync(absPhaseDir).sort(); | |
| for (const f of files) { | |
| if (isPlanFile(f) && artifacts.plan === null) { | |
| artifacts.plan = path.join(phaseDir, f); | |
| } else if (isSummaryFile(f) && artifacts.summary === null) { | |
| artifacts.summary = path.join(phaseDir, f); | |
| } else if ((f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md') && artifacts.research === null) { | |
| artifacts.research = path.join(phaseDir, f); | |
| } else if ((f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md') && artifacts.context === null) { | |
| artifacts.context = path.join(phaseDir, f); | |
| } else if ((f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md') && artifacts.verification === null) { |
| state: { | ||
| current_focus: currentPhaseName ?? null, | ||
| position, | ||
| status, | ||
| }, |
There was a problem hiding this comment.
state.current_focus is populated with Current Phase Name, which largely duplicates phase.name and doesn't correspond to a STATE.md field named "Current Focus". This makes the JSON schema ambiguous for skill consumers; consider either renaming the field to reflect its source (e.g., current_phase_name) or populating it from an actual STATE.md "focus" field if one exists.
| export function cmdSkillContext(cwd: string, skillName: string, raw: boolean): void { | ||
| const planningExists = pathExistsInternal(cwd, '.planning'); | ||
|
|
||
| if (!planningExists) { | ||
| const result: SkillContextResult = { | ||
| skill_name: skillName, | ||
| planning_dir: null, | ||
| phase: { number: null, name: null, directory: null }, | ||
| state: { current_focus: null, position: null, status: null }, | ||
| blockers: [], | ||
| decisions: [], | ||
| artifacts: { plan: null, summary: null, research: null, context: null, verification: null }, | ||
| config: { model_profile: 'balanced', commit_docs: true, branching_strategy: 'none' }, | ||
| }; | ||
| output(result, raw); | ||
| return; | ||
| } |
There was a problem hiding this comment.
New skill-context behavior is not covered by existing CLI e2e tests. Consider adding a test case (similar to packages/cli/tests/e2e/tools.test.ts) that runs skill-context against (1) a mock project with .planning/STATE.md and phase artifacts and asserts the returned JSON fields, and (2) a directory without .planning/ and asserts the null-field fallback shape.
Summary
skill-contextCLI command that returns MAXSIM project state as JSONnode cli.cjs skill-context <skillName>to get current phase, STATE.md fields, blockers, decisions, artifact paths, and config.planning/directory does not exist, so skills work even without MAXSIM project setupTest plan
npx vitest run-- 54 tests)npm run build)node dist/cli.cjs skill-context test-skillin a project with.planning/and verify JSON output.planning/and verify graceful null-field response🤖 Generated with Claude Code