supipowers 1.5.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -8
- package/bin/install.mjs +20 -5
- package/bin/install.ts +95 -0
- package/package.json +8 -4
- package/skills/context-mode/SKILL.md +17 -10
- package/skills/harness/SKILL.md +94 -0
- package/skills/ui-design/SKILL.md +63 -0
- package/skills/ui-design/sub-agent-templates/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/design-critic.md +46 -0
- package/skills/ui-design/sub-agent-templates/pencil/component-builder.md +29 -0
- package/skills/ui-design/sub-agent-templates/pencil/design-critic.md +42 -0
- package/skills/ui-design/sub-agent-templates/pencil/section-assembler.md +27 -0
- package/skills/ui-design/sub-agent-templates/section-assembler.md +27 -0
- package/skills/ultraplan-discover/SKILL.md +96 -0
- package/skills/ultraplan-intake/SKILL.md +89 -0
- package/skills/ultraplan-research/SKILL.md +129 -0
- package/skills/ultraplan-review/SKILL.md +86 -0
- package/skills/ultraplan-review-scope/SKILL.md +111 -0
- package/skills/ultraplan-review-structure/SKILL.md +120 -0
- package/skills/ultraplan-review-tdd/SKILL.md +142 -0
- package/skills/ultraplan-scout/SKILL.md +110 -0
- package/skills/ultraplan-synthesize/SKILL.md +124 -0
- package/src/{quality/ai-session.ts → ai/final-message.ts} +27 -0
- package/src/ai/schema-text.ts +129 -0
- package/src/ai/structured-output.ts +274 -0
- package/src/ai/template.ts +27 -0
- package/src/bootstrap.ts +63 -28
- package/src/commands/agents.ts +131 -42
- package/src/commands/ai-review.ts +251 -30
- package/src/commands/clear.ts +434 -0
- package/src/commands/commit.ts +1 -0
- package/src/commands/config.ts +242 -44
- package/src/commands/context.ts +55 -28
- package/src/commands/doctor.ts +234 -6
- package/src/commands/fix-pr.ts +306 -131
- package/src/commands/generate.ts +111 -21
- package/src/commands/memory.ts +192 -0
- package/src/commands/model-picker.ts +28 -21
- package/src/commands/model.ts +18 -8
- package/src/commands/optimize-context.ts +408 -29
- package/src/commands/plan.ts +2 -0
- package/src/commands/qa.ts +312 -137
- package/src/commands/release.ts +259 -76
- package/src/commands/review.ts +293 -59
- package/src/commands/status.ts +200 -13
- package/src/commands/supi.ts +3 -35
- package/src/commands/ui-design.ts +394 -0
- package/src/commands/ultraplan.ts +1518 -0
- package/src/commands/update.ts +86 -0
- package/src/config/defaults.ts +62 -0
- package/src/config/loader.ts +448 -60
- package/src/config/schema.ts +108 -2
- package/src/context/optimizer.ts +25 -33
- package/src/context/rule-renderer.ts +223 -0
- package/src/context/savings.ts +258 -0
- package/src/context/startup-check.ts +380 -0
- package/src/context/startup-optimizer.ts +355 -0
- package/src/context/tokenignore.ts +146 -0
- package/src/context-mode/cache-handle.ts +49 -0
- package/src/context-mode/cache-preview.ts +71 -0
- package/src/context-mode/cache-store.ts +738 -0
- package/src/context-mode/compressor.ts +131 -26
- package/src/context-mode/dedup.ts +108 -0
- package/src/context-mode/detector.ts +35 -4
- package/src/context-mode/event-extractor.ts +14 -12
- package/src/context-mode/event-store.ts +91 -36
- package/src/context-mode/hooks.ts +798 -56
- package/src/context-mode/knowledge/store.ts +255 -11
- package/src/context-mode/memory-store.ts +325 -0
- package/src/context-mode/metrics-recorder.ts +158 -0
- package/src/context-mode/metrics-store.ts +765 -0
- package/src/context-mode/model.ts +24 -0
- package/src/context-mode/processor-keys.ts +29 -0
- package/src/context-mode/processors/build.ts +66 -0
- package/src/context-mode/processors/docker.ts +57 -0
- package/src/context-mode/processors/git.ts +111 -0
- package/src/context-mode/processors/json.ts +112 -0
- package/src/context-mode/processors/k8s.ts +67 -0
- package/src/context-mode/processors/lint.ts +67 -0
- package/src/context-mode/processors/log.ts +86 -0
- package/src/context-mode/processors/registry.ts +116 -0
- package/src/context-mode/processors/test-runner.ts +102 -0
- package/src/context-mode/processors/types.ts +20 -0
- package/src/context-mode/repomap.ts +400 -0
- package/src/context-mode/routing.ts +97 -24
- package/src/context-mode/sandbox/runners.ts +5 -1
- package/src/context-mode/snapshot-builder.ts +106 -11
- package/src/context-mode/source-hash.ts +173 -0
- package/src/context-mode/tool-name.ts +11 -0
- package/src/context-mode/tools.ts +654 -22
- package/src/context-mode/web/fetcher.ts +31 -12
- package/src/debug/logger.ts +2 -1
- package/src/deps/registry.ts +1 -1
- package/src/discipline/failure-summarizer.ts +170 -0
- package/src/discipline/failure-taxonomy.ts +131 -0
- package/src/discipline/workflow-invariants.ts +125 -0
- package/src/discovery/index.ts +31 -0
- package/src/discovery/lsp.ts +87 -0
- package/src/discovery/rank.ts +144 -0
- package/src/discovery/sources.ts +89 -0
- package/src/discovery/workflow.ts +87 -0
- package/src/docs/contracts.ts +39 -0
- package/src/docs/drift.ts +117 -87
- package/src/fix-pr/assessment.ts +200 -0
- package/src/fix-pr/contracts.ts +47 -0
- package/src/fix-pr/fetch-comments.ts +80 -0
- package/src/fix-pr/prompt-builder.ts +58 -40
- package/src/fix-pr/scripts/exec.ts +34 -0
- package/src/fix-pr/scripts/trigger-review.ts +106 -0
- package/src/fix-pr/scripts/wait-and-check.ts +108 -0
- package/src/fix-pr/types.ts +4 -0
- package/src/git/branch-finish.ts +5 -0
- package/src/git/commit-contract.ts +83 -0
- package/src/git/commit.ts +121 -184
- package/src/git/status.ts +62 -8
- package/src/harness/anti_slop/architecture-parser.ts +210 -0
- package/src/harness/anti_slop/backend-factory.ts +30 -0
- package/src/harness/anti_slop/backend.ts +140 -0
- package/src/harness/anti_slop/desloppify-adapter.ts +319 -0
- package/src/harness/anti_slop/fallow-adapter.ts +305 -0
- package/src/harness/anti_slop/installer.ts +227 -0
- package/src/harness/anti_slop/queue.ts +216 -0
- package/src/harness/anti_slop/recommend.ts +84 -0
- package/src/harness/anti_slop/score.ts +180 -0
- package/src/harness/anti_slop/synthetic-edit-test.ts +128 -0
- package/src/harness/artifacts/agents-md.ts +88 -0
- package/src/harness/artifacts/checks-wiring.ts +57 -0
- package/src/harness/artifacts/docs-tree.ts +79 -0
- package/src/harness/artifacts/lint-configs.ts +136 -0
- package/src/harness/artifacts/review-agents.ts +67 -0
- package/src/harness/bare-entry.ts +108 -0
- package/src/harness/command.ts +1010 -0
- package/src/harness/default-agents/design.md +23 -0
- package/src/harness/default-agents/discover.md +18 -0
- package/src/harness/default-agents/implement.md +24 -0
- package/src/harness/default-agents/plan.md +19 -0
- package/src/harness/default-agents/research.md +21 -0
- package/src/harness/default-agents/validate.md +22 -0
- package/src/harness/gc/reporter.ts +28 -0
- package/src/harness/gc/runner.ts +136 -0
- package/src/harness/hooks/layer-context-inject.ts +155 -0
- package/src/harness/hooks/post-session-sweep.ts +130 -0
- package/src/harness/hooks/pre-edit-dupe-probe.ts +224 -0
- package/src/harness/hooks/register.ts +118 -0
- package/src/harness/model.ts +117 -0
- package/src/harness/pipeline.ts +348 -0
- package/src/harness/project-paths.ts +235 -0
- package/src/harness/stage-runner.ts +107 -0
- package/src/harness/stages/design.ts +386 -0
- package/src/harness/stages/discover.ts +454 -0
- package/src/harness/stages/implement.ts +162 -0
- package/src/harness/stages/plan.ts +335 -0
- package/src/harness/stages/research.ts +263 -0
- package/src/harness/stages/validate.ts +684 -0
- package/src/harness/storage.ts +467 -0
- package/src/harness/tools.ts +426 -0
- package/src/lsp/bridge.ts +56 -95
- package/src/lsp/capabilities.ts +108 -0
- package/src/lsp/contracts.ts +35 -0
- package/src/lsp/detector.ts +8 -12
- package/src/markdown-frontmatter.ts +68 -0
- package/src/mempalace/bridge.ts +135 -0
- package/src/mempalace/config.ts +75 -0
- package/src/mempalace/format.ts +163 -0
- package/src/mempalace/hooks.ts +370 -0
- package/src/mempalace/installer-helper.ts +194 -0
- package/src/mempalace/python/mempalace_bridge.py +440 -0
- package/src/mempalace/runtime.ts +565 -0
- package/src/mempalace/schema.ts +268 -0
- package/src/mempalace/session-summary.ts +198 -0
- package/src/mempalace/tool.ts +186 -0
- package/src/mempalace/uv.ts +256 -0
- package/src/migrate/runner.ts +354 -0
- package/src/planning/approval-flow.ts +206 -9
- package/src/planning/plan-writer-prompt.ts +4 -3
- package/src/planning/planning-ask-tool.ts +39 -0
- package/src/planning/render-markdown.ts +74 -0
- package/src/planning/spec.ts +42 -0
- package/src/planning/system-prompt.ts +11 -8
- package/src/planning/validate.ts +84 -0
- package/src/platform/omp.ts +15 -2
- package/src/platform/system-prompt.ts +37 -0
- package/src/platform/test-utils.ts +3 -0
- package/src/platform/types.ts +6 -1
- package/src/qa/config.ts +12 -6
- package/src/qa/detect-app-type.ts +13 -6
- package/src/qa/matrix.ts +12 -6
- package/src/qa/prompt-builder.ts +28 -30
- package/src/qa/scripts/dev-server-utils.ts +72 -0
- package/src/qa/scripts/run-e2e-tests.ts +226 -0
- package/src/qa/scripts/start-dev-server.ts +138 -0
- package/src/qa/scripts/stop-dev-server.ts +77 -0
- package/src/qa/session.ts +13 -7
- package/src/quality/ai-setup.ts +27 -25
- package/src/quality/contracts.ts +34 -0
- package/src/quality/gates/ai-review.ts +20 -58
- package/src/quality/gates/command.ts +249 -46
- package/src/quality/review-gates.ts +18 -2
- package/src/quality/runner.ts +63 -22
- package/src/quality/schemas.ts +37 -2
- package/src/quality/setup.ts +96 -16
- package/src/release/changelog.ts +1 -1
- package/src/release/channels/custom.ts +13 -3
- package/src/release/channels/types.ts +5 -0
- package/src/release/contracts.ts +90 -0
- package/src/release/executor.ts +122 -45
- package/src/release/prompt.ts +18 -2
- package/src/release/targets.ts +86 -0
- package/src/release/version.ts +96 -71
- package/src/review/agent-loader.ts +221 -109
- package/src/review/fixer.ts +10 -6
- package/src/review/multi-agent-runner.ts +114 -13
- package/src/review/output.ts +12 -139
- package/src/review/runner.ts +12 -6
- package/src/review/scope.ts +144 -24
- package/src/review/types.ts +1 -20
- package/src/review/validator.ts +12 -6
- package/src/storage/fix-pr-sessions.ts +21 -14
- package/src/storage/plans.ts +14 -5
- package/src/storage/qa-sessions.ts +25 -19
- package/src/storage/reliability-metrics.ts +180 -0
- package/src/storage/reports.ts +8 -7
- package/src/storage/review-sessions.ts +55 -20
- package/src/tool-catalog/active-tool-controller.ts +164 -0
- package/src/tool-catalog/active-tool-planner.ts +212 -0
- package/src/tool-catalog/tool-groups.ts +102 -0
- package/src/types.ts +1399 -5
- package/src/ui-design/backend-adapter.ts +78 -0
- package/src/ui-design/backends/local-html.ts +82 -0
- package/src/ui-design/backends/pencil-mcp.ts +111 -0
- package/src/ui-design/components-scanner.ts +124 -0
- package/src/ui-design/config.ts +55 -0
- package/src/ui-design/pen-scanner.ts +95 -0
- package/src/ui-design/pen-selector.ts +72 -0
- package/src/ui-design/prompt-builder.ts +73 -0
- package/src/ui-design/scanner.ts +136 -0
- package/src/ui-design/session.ts +974 -0
- package/src/ui-design/system-prompt.ts +312 -0
- package/src/ui-design/tokens-scanner.ts +181 -0
- package/src/ui-design/types.ts +96 -0
- package/src/ultraplan/agent-catalog.ts +522 -0
- package/src/ultraplan/authoring/agent-catalog.ts +310 -0
- package/src/ultraplan/authoring/authoring-tools.ts +552 -0
- package/src/ultraplan/authoring/command-handlers.ts +339 -0
- package/src/ultraplan/authoring/markdown.ts +510 -0
- package/src/ultraplan/authoring/model.ts +162 -0
- package/src/ultraplan/authoring/pipeline.ts +319 -0
- package/src/ultraplan/authoring/stage-runner.ts +141 -0
- package/src/ultraplan/authoring/stages/approve.ts +249 -0
- package/src/ultraplan/authoring/stages/discover.ts +289 -0
- package/src/ultraplan/authoring/stages/intake.ts +203 -0
- package/src/ultraplan/authoring/stages/research.ts +399 -0
- package/src/ultraplan/authoring/stages/review.ts +333 -0
- package/src/ultraplan/authoring/stages/scout.ts +188 -0
- package/src/ultraplan/authoring/stages/synthesize.ts +348 -0
- package/src/ultraplan/authoring/storage.ts +594 -0
- package/src/ultraplan/authoring/synth-gate.ts +165 -0
- package/src/ultraplan/authoring-draft.ts +653 -0
- package/src/ultraplan/authoring-persist.ts +180 -0
- package/src/ultraplan/authoring-tool.ts +608 -0
- package/src/ultraplan/authoring-wizard.ts +587 -0
- package/src/ultraplan/batch/merge.ts +98 -0
- package/src/ultraplan/batch/planner.ts +150 -0
- package/src/ultraplan/batch/presenter.ts +97 -0
- package/src/ultraplan/batch/storage.ts +420 -0
- package/src/ultraplan/batch/supervisor.ts +317 -0
- package/src/ultraplan/batch/worker.ts +26 -0
- package/src/ultraplan/batch/worktree.ts +110 -0
- package/src/ultraplan/contracts.ts +1593 -0
- package/src/ultraplan/default-agents/authoring/discoverer.md +12 -0
- package/src/ultraplan/default-agents/authoring/intake.md +12 -0
- package/src/ultraplan/default-agents/authoring/planner.md +12 -0
- package/src/ultraplan/default-agents/authoring/researcher.md +12 -0
- package/src/ultraplan/default-agents/authoring/scope-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/scout.md +12 -0
- package/src/ultraplan/default-agents/authoring/structure-checker.md +12 -0
- package/src/ultraplan/default-agents/authoring/tdd-checker.md +12 -0
- package/src/ultraplan/default-agents/backend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-executor.md +10 -0
- package/src/ultraplan/default-agents/backend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/backend-tester.md +10 -0
- package/src/ultraplan/default-agents/frontend-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-executor.md +10 -0
- package/src/ultraplan/default-agents/frontend-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/frontend-tester.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-domain-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-executor.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-stack-reviewer.md +10 -0
- package/src/ultraplan/default-agents/infrastructure-tester.md +10 -0
- package/src/ultraplan/execution/contract.ts +71 -0
- package/src/ultraplan/execution/policy.ts +217 -0
- package/src/ultraplan/execution/runtime-tools.ts +107 -0
- package/src/ultraplan/execution/session-runner.ts +281 -0
- package/src/ultraplan/next-router.ts +85 -0
- package/src/ultraplan/presenter.ts +359 -0
- package/src/ultraplan/project-paths.ts +342 -0
- package/src/ultraplan/runtime/active-execution.ts +72 -0
- package/src/ultraplan/runtime/apply-mutation.ts +416 -0
- package/src/ultraplan/runtime/blockers.ts +243 -0
- package/src/ultraplan/runtime/hook-bridge.ts +486 -0
- package/src/ultraplan/runtime/launch-context.ts +207 -0
- package/src/ultraplan/runtime/migration.ts +524 -0
- package/src/ultraplan/runtime/normalize.ts +281 -0
- package/src/ultraplan/runtime/proof.ts +260 -0
- package/src/ultraplan/runtime/reducer.ts +416 -0
- package/src/ultraplan/runtime/repair.ts +251 -0
- package/src/ultraplan/runtime/tracker-storage.ts +368 -0
- package/src/ultraplan/session-selection.ts +291 -0
- package/src/ultraplan/storage.ts +374 -0
- package/src/utils/editor.ts +38 -0
- package/src/utils/executable.ts +80 -0
- package/src/utils/paths.ts +1 -20
- package/src/utils/shell.ts +31 -0
- package/src/visual/companion.ts +2 -1
- package/src/visual/scripts/frame-template.html +60 -0
- package/src/visual/scripts/index.js +59 -13
- package/src/visual/scripts/package.json +3 -0
- package/src/visual/start-server.ts +2 -1
- package/src/workspace/git-scope.ts +64 -0
- package/src/workspace/locks.ts +23 -0
- package/src/workspace/package-manager.ts +117 -0
- package/src/workspace/path-mapping.ts +75 -0
- package/src/workspace/project-slug.ts +92 -0
- package/src/workspace/repo-root.ts +137 -0
- package/src/workspace/selector.ts +115 -0
- package/src/workspace/state-paths.ts +118 -0
- package/src/workspace/targets.ts +313 -0
- package/src/fix-pr/scripts/diff-comments.sh +0 -33
- package/src/fix-pr/scripts/fetch-pr-comments.sh +0 -25
- package/src/fix-pr/scripts/trigger-review.sh +0 -36
- package/src/fix-pr/scripts/wait-and-check.sh +0 -37
- package/src/qa/scripts/detect-app-type.sh +0 -68
- package/src/qa/scripts/discover-routes.sh +0 -143
- package/src/qa/scripts/run-e2e-tests.sh +0 -131
- package/src/qa/scripts/start-dev-server.sh +0 -46
- package/src/qa/scripts/stop-dev-server.sh +0 -36
- package/src/review/prompts/fix-output-schema.md +0 -18
- package/src/review/prompts/review-output-schema.md +0 -38
- package/src/review/template.ts +0 -15
- /package/src/{review → ai}/prompts/invalid-output-retry.md +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { Platform } from "../platform/types.js";
|
|
2
2
|
import type { DebugLogger } from "../debug/logger.js";
|
|
3
|
-
import type { ResolvedModel } from "../types.js";
|
|
3
|
+
import type { Plan, ResolvedModel } from "../types.js";
|
|
4
4
|
import type { PlanningSystemPromptOptions } from "./system-prompt.js";
|
|
5
5
|
import { applyModelOverride } from "../config/model-resolver.js";
|
|
6
|
-
import { listPlans, readPlanFile } from "../storage/plans.js";
|
|
6
|
+
import { listPlans, parsePlan, readPlanFile } from "../storage/plans.js";
|
|
7
|
+
import { validatePlanMarkdown } from "./validate.js";
|
|
8
|
+
import { getProjectStatePath } from "../workspace/state-paths.js";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { appendReliabilityRecord } from "../storage/reliability-metrics.js";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* Plan approval flow state.
|
|
@@ -79,13 +83,135 @@ export function getPlanningDebugLogger(): DebugLogger | null {
|
|
|
79
83
|
return planningDebugLogger;
|
|
80
84
|
}
|
|
81
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Mirrors OMP 14.5.11+'s `todo_write` payload shape just enough to hand the
|
|
88
|
+
* agent a ready-to-execute payload after plan approval.
|
|
89
|
+
*
|
|
90
|
+
* The canonical schema lives in OMP \u2014 keep this type local; we only construct
|
|
91
|
+
* the payload to embed in a prompt string. Task identity is the task content
|
|
92
|
+
* verbatim; later progress updates (`start`/`done`/`note`) refer to that exact
|
|
93
|
+
* string, not synthetic ids.
|
|
94
|
+
*/
|
|
95
|
+
type TodoWriteOp =
|
|
96
|
+
| {
|
|
97
|
+
op: "init";
|
|
98
|
+
list: Array<{ phase: string; items: string[] }>;
|
|
99
|
+
}
|
|
100
|
+
| { op: "note"; task: string; text: string };
|
|
101
|
+
|
|
102
|
+
/** Cap individual task labels so the embedded payload stays bounded. */
|
|
103
|
+
const TODO_WRITE_TASK_LABEL_MAX_CHARS = 200;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Single phase name supipowers' planner emits today. Imported into the
|
|
107
|
+
* execution prompt so the prose stays in lock-step with the JSON payload \u2014
|
|
108
|
+
* change here and `buildExecutionPrompt` follows automatically.
|
|
109
|
+
*/
|
|
110
|
+
const PLAN_PHASE_NAME = "Implementation";
|
|
111
|
+
|
|
112
|
+
function truncateTaskLabel(label: string): string {
|
|
113
|
+
if (label.length <= TODO_WRITE_TASK_LABEL_MAX_CHARS) return label;
|
|
114
|
+
return `${label.slice(0, TODO_WRITE_TASK_LABEL_MAX_CHARS - 1).trimEnd()}\u2026`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function appendTaskLabelOrdinal(label: string, ordinal: number): string {
|
|
118
|
+
const suffix = ` (${ordinal})`;
|
|
119
|
+
if (label.length + suffix.length <= TODO_WRITE_TASK_LABEL_MAX_CHARS) {
|
|
120
|
+
return `${label}${suffix}`;
|
|
121
|
+
}
|
|
122
|
+
const prefixMax = TODO_WRITE_TASK_LABEL_MAX_CHARS - suffix.length - 1;
|
|
123
|
+
return `${label.slice(0, prefixMax).trimEnd()}\u2026${suffix}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function uniqueTaskLabels(names: string[]): string[] {
|
|
127
|
+
const used = new Set<string>();
|
|
128
|
+
const nextOrdinalByBase = new Map<string, number>();
|
|
129
|
+
return names.map((name) => {
|
|
130
|
+
const base = truncateTaskLabel(name);
|
|
131
|
+
let label = base;
|
|
132
|
+
if (used.has(label)) {
|
|
133
|
+
let ordinal = nextOrdinalByBase.get(base) ?? 2;
|
|
134
|
+
do {
|
|
135
|
+
label = appendTaskLabelOrdinal(base, ordinal);
|
|
136
|
+
ordinal += 1;
|
|
137
|
+
} while (used.has(label));
|
|
138
|
+
nextOrdinalByBase.set(base, ordinal);
|
|
139
|
+
} else {
|
|
140
|
+
nextOrdinalByBase.set(base, 2);
|
|
141
|
+
}
|
|
142
|
+
used.add(label);
|
|
143
|
+
return label;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Build the canonical `todo_write` ops payload from a parsed plan.
|
|
149
|
+
*
|
|
150
|
+
* Empty plans return `{ ops: [] }` so callers can skip emit cleanly.
|
|
151
|
+
* Non-empty plans always start with a single `init` op containing one phase
|
|
152
|
+
* (`Implementation`) whose `items` is one task content string per `plan.tasks`
|
|
153
|
+
* entry. Task names are truncated and de-duplicated before they become todo
|
|
154
|
+
* identities. Tasks that carry acceptance criteria get a follow-up `note` op
|
|
155
|
+
* whose `task` field is the exact item label, keeping note targets in lock-step
|
|
156
|
+
*/
|
|
157
|
+
export function buildTodoWriteOpsForPlan(plan: Plan): { ops: TodoWriteOp[] } {
|
|
158
|
+
if (plan.tasks.length === 0) return { ops: [] };
|
|
159
|
+
|
|
160
|
+
const items = uniqueTaskLabels(plan.tasks.map((task) => task.name));
|
|
161
|
+
|
|
162
|
+
const ops: TodoWriteOp[] = [
|
|
163
|
+
{
|
|
164
|
+
op: "init",
|
|
165
|
+
list: [{ phase: PLAN_PHASE_NAME, items }],
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
for (const [index, task] of plan.tasks.entries()) {
|
|
170
|
+
const trimmed = task.criteria.trim();
|
|
171
|
+
if (!trimmed) continue;
|
|
172
|
+
// Note targets MUST equal the item label verbatim \u2014 task identity is the
|
|
173
|
+
// task content string, never a synthetic id.
|
|
174
|
+
ops.push({ op: "note", task: items[index], text: trimmed });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { ops };
|
|
178
|
+
}
|
|
179
|
+
|
|
82
180
|
/**
|
|
83
181
|
* Build the execution handoff prompt from an approved plan.
|
|
84
182
|
*
|
|
85
183
|
* Mirrors OMP's `plan-mode-approved.md` template: critical directive
|
|
86
184
|
* to execute, the full plan content, and step-by-step instructions.
|
|
185
|
+
*
|
|
186
|
+
* When `plan` is provided and has tasks, the prompt also embeds the
|
|
187
|
+
* exact `todo_write` payload the agent must call before doing any work.
|
|
87
188
|
*/
|
|
88
|
-
function buildExecutionPrompt(
|
|
189
|
+
function buildExecutionPrompt(
|
|
190
|
+
planContent: string,
|
|
191
|
+
planPath: string,
|
|
192
|
+
plan?: Plan,
|
|
193
|
+
): string {
|
|
194
|
+
const todoBlock: string[] = [];
|
|
195
|
+
if (plan && plan.tasks.length > 0) {
|
|
196
|
+
const payload = buildTodoWriteOpsForPlan(plan);
|
|
197
|
+
const initOp = payload.ops.find(
|
|
198
|
+
(op): op is Extract<TodoWriteOp, { op: "init" }> => op.op === "init",
|
|
199
|
+
);
|
|
200
|
+
const phaseName = initOp?.list[0]?.phase ?? PLAN_PHASE_NAME;
|
|
201
|
+
todoBlock.push(
|
|
202
|
+
"",
|
|
203
|
+
"## Initialize todo tracker",
|
|
204
|
+
"",
|
|
205
|
+
"Before any other work, call `todo_write` with exactly this payload:",
|
|
206
|
+
"",
|
|
207
|
+
"```json",
|
|
208
|
+
JSON.stringify(payload, null, 2),
|
|
209
|
+
"```",
|
|
210
|
+
"",
|
|
211
|
+
`Task identity is the task content verbatim. Later progress updates (\`start\`, \`done\`, \`note\`) MUST pass \`task\` equal to the exact item string above; phase updates MUST pass \`phase: "${phaseName}"\`. Mark the first task \`in_progress\` and proceed.`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
89
215
|
return [
|
|
90
216
|
"<critical>",
|
|
91
217
|
"Plan approved. You **MUST** execute it now.",
|
|
@@ -100,9 +226,8 @@ function buildExecutionPrompt(planContent: string, planPath: string): string {
|
|
|
100
226
|
"<instruction>",
|
|
101
227
|
`You **MUST** execute this plan step by step from \`${planPath}\`.`,
|
|
102
228
|
"You **MUST** verify each step before proceeding to the next.",
|
|
103
|
-
"Before execution, you **MUST** initialize todo tracking with the task list.",
|
|
104
|
-
"After each completed step, immediately update progress so it stays visible.",
|
|
105
229
|
"</instruction>",
|
|
230
|
+
...todoBlock,
|
|
106
231
|
"",
|
|
107
232
|
"<critical>",
|
|
108
233
|
"You **MUST** keep going until complete. This matters.",
|
|
@@ -128,8 +253,9 @@ async function executeApproveFlow(
|
|
|
128
253
|
newSession: ((options?: any) => Promise<{ cancelled: boolean }>) | null,
|
|
129
254
|
resolvedModel: ResolvedModel | null,
|
|
130
255
|
debugLogger: DebugLogger | null,
|
|
256
|
+
plan: Plan | null,
|
|
131
257
|
): Promise<void> {
|
|
132
|
-
const prompt = buildExecutionPrompt(planContent, planPath);
|
|
258
|
+
const prompt = buildExecutionPrompt(planContent, planPath, plan ?? undefined);
|
|
133
259
|
debugLogger?.log("execution_handoff_started", {
|
|
134
260
|
planPath,
|
|
135
261
|
promptLength: prompt.length,
|
|
@@ -211,7 +337,76 @@ export function registerPlanApprovalHook(platform: Platform): void {
|
|
|
211
337
|
return;
|
|
212
338
|
}
|
|
213
339
|
|
|
214
|
-
|
|
340
|
+
// Schema-first validation: the plan must parse into a valid PlanSpec.
|
|
341
|
+
// Invalid plans trigger a retry steer — no approval UI until the agent
|
|
342
|
+
// produces an artifact whose task list matches the PlanSpec contract.
|
|
343
|
+
//
|
|
344
|
+
// We validate but do NOT canonicalize the on-disk file. Today's plan
|
|
345
|
+
// writer produces rich markdown (architecture, per-task TDD steps) that
|
|
346
|
+
// the parser intentionally does not capture. Rewriting the file from the
|
|
347
|
+
// parsed PlanSpec would strip that structure. The schema is the
|
|
348
|
+
// validation gate; markdown stays the user-visible form until a future
|
|
349
|
+
// phase lifts the agent to write PlanSpec directly.
|
|
350
|
+
const validated = validatePlanMarkdown(planContent, planName);
|
|
351
|
+
if (!validated.output) {
|
|
352
|
+
debugLogger?.log("approval_flow_plan_invalid", {
|
|
353
|
+
planName,
|
|
354
|
+
error: validated.error,
|
|
355
|
+
errors: validated.errors,
|
|
356
|
+
});
|
|
357
|
+
try {
|
|
358
|
+
appendReliabilityRecord(platform.paths, planCwd, {
|
|
359
|
+
ts: new Date().toISOString(),
|
|
360
|
+
command: "plan",
|
|
361
|
+
operation: "plan-spec",
|
|
362
|
+
outcome: "blocked",
|
|
363
|
+
attempts: 1,
|
|
364
|
+
reason: validated.error ?? "Plan validation failed.",
|
|
365
|
+
cwd: planCwd,
|
|
366
|
+
});
|
|
367
|
+
} catch {}
|
|
368
|
+
plansBefore = plansNow;
|
|
369
|
+
const steer = [
|
|
370
|
+
`The plan you just wrote to \`${path.join(getProjectStatePath(platform.paths, planCwd, "plans"), planName)}\` does not match the required schema.`,
|
|
371
|
+
"",
|
|
372
|
+
"Validation errors:",
|
|
373
|
+
...validated.errors.map((err) => `- ${err.path}: ${err.message}`),
|
|
374
|
+
"",
|
|
375
|
+
"Fix the plan and rewrite the file so every task includes id, name, files, criteria, and complexity (small|medium|large).",
|
|
376
|
+
].join("\n");
|
|
377
|
+
platform.sendMessage(
|
|
378
|
+
{
|
|
379
|
+
customType: "supi-plan-invalid",
|
|
380
|
+
content: [{ type: "text", text: steer }],
|
|
381
|
+
display: "none",
|
|
382
|
+
},
|
|
383
|
+
{ deliverAs: "steer", triggerTurn: true },
|
|
384
|
+
);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const canonicalContent = planContent;
|
|
389
|
+
const planPath = path.join(getProjectStatePath(platform.paths, planCwd, "plans"), planName);
|
|
390
|
+
let parsedPlan: Plan | null = null;
|
|
391
|
+
try {
|
|
392
|
+
parsedPlan = parsePlan(planContent, planPath);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
debugLogger?.log("approval_flow_plan_parse_failed", {
|
|
395
|
+
planName,
|
|
396
|
+
planPath,
|
|
397
|
+
error: error instanceof Error ? error.message : String(error),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
appendReliabilityRecord(platform.paths, planCwd, {
|
|
402
|
+
ts: new Date().toISOString(),
|
|
403
|
+
command: "plan",
|
|
404
|
+
operation: "plan-spec",
|
|
405
|
+
outcome: "ok",
|
|
406
|
+
attempts: 1,
|
|
407
|
+
cwd: planCwd,
|
|
408
|
+
});
|
|
409
|
+
} catch {}
|
|
215
410
|
const approvalOptions = [
|
|
216
411
|
"Approve and execute",
|
|
217
412
|
"Refine plan",
|
|
@@ -238,11 +433,12 @@ export function registerPlanApprovalHook(platform: Platform): void {
|
|
|
238
433
|
await executeApproveFlow(
|
|
239
434
|
platform,
|
|
240
435
|
ctx,
|
|
241
|
-
|
|
436
|
+
canonicalContent,
|
|
242
437
|
planPath,
|
|
243
438
|
executionNewSession,
|
|
244
439
|
executionModel,
|
|
245
440
|
debugLogger,
|
|
441
|
+
parsedPlan,
|
|
246
442
|
);
|
|
247
443
|
} else if (choice === "Refine plan") {
|
|
248
444
|
// Keep planning active, let user type refinement.
|
|
@@ -260,11 +456,12 @@ export function registerPlanApprovalHook(platform: Platform): void {
|
|
|
260
456
|
await executeApproveFlow(
|
|
261
457
|
platform,
|
|
262
458
|
ctx,
|
|
263
|
-
|
|
459
|
+
canonicalContent,
|
|
264
460
|
planPath,
|
|
265
461
|
executionNewSession,
|
|
266
462
|
executionModel,
|
|
267
463
|
debugLogger,
|
|
464
|
+
parsedPlan,
|
|
268
465
|
);
|
|
269
466
|
} else {
|
|
270
467
|
debugLogger?.log("approval_flow_refine_requested", {
|
|
@@ -3,7 +3,8 @@ import { PLAN_CODE_CONTENT_REQUIREMENTS, formatPlanReviewCategorySummary } from
|
|
|
3
3
|
|
|
4
4
|
export interface PlanWriterOptions {
|
|
5
5
|
specPath: string;
|
|
6
|
-
|
|
6
|
+
/** Absolute path to the directory where the plan must be saved. */
|
|
7
|
+
plansDir: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -18,7 +19,7 @@ export interface PlanWriterOptions {
|
|
|
18
19
|
* - Execution handoff
|
|
19
20
|
*/
|
|
20
21
|
export function buildPlanWriterPrompt(options: PlanWriterOptions): string {
|
|
21
|
-
const { specPath,
|
|
22
|
+
const { specPath, plansDir } = options;
|
|
22
23
|
|
|
23
24
|
const sections: string[] = [
|
|
24
25
|
"You are writing a comprehensive implementation plan from an approved design spec.",
|
|
@@ -134,7 +135,7 @@ export function buildPlanWriterPrompt(options: PlanWriterOptions): string {
|
|
|
134
135
|
// ── Save Location ────────────────────────────────────────────
|
|
135
136
|
"## Step 5: Save Plan",
|
|
136
137
|
"",
|
|
137
|
-
`Save the plan to \`${
|
|
138
|
+
`Save the plan to \`${plansDir}/YYYY-MM-DD-<feature-name>.md\``,
|
|
138
139
|
"",
|
|
139
140
|
|
|
140
141
|
// ── Execution Handoff ────────────────────────────────────────
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Platform } from "../platform/types.js";
|
|
2
|
+
import { isPlanningActive } from "./approval-flow.js";
|
|
3
|
+
import { isUiDesignActive, recordUiDesignReviewApproval } from "../ui-design/session.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Register a `planning_ask` tool — identical to the built-in `ask` tool
|
|
@@ -66,6 +68,7 @@ export function registerPlanningAskTool(platform: Platform): void {
|
|
|
66
68
|
});
|
|
67
69
|
|
|
68
70
|
const selected = choice ?? labels[params.recommended ?? 0] ?? labels[0];
|
|
71
|
+
recordUiDesignReviewApproval(params.question, labels, selected);
|
|
69
72
|
|
|
70
73
|
return {
|
|
71
74
|
content: [
|
|
@@ -79,3 +82,39 @@ export function registerPlanningAskTool(platform: Platform): void {
|
|
|
79
82
|
},
|
|
80
83
|
});
|
|
81
84
|
}
|
|
85
|
+
|
|
86
|
+
function getAskRedirectReason(): string | null {
|
|
87
|
+
if (isPlanningActive()) {
|
|
88
|
+
return "Planning mode: use the `planning_ask` tool instead of `ask`. `planning_ask` has no timeout so the user can think without pressure.";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (isUiDesignActive()) {
|
|
92
|
+
return "UI-design mode: use the `planning_ask` tool instead of `ask`. The Design Director workflow requires auditable gated responses through `planning_ask`.";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Register a tool_call guard that blocks the generic `ask` tool during
|
|
100
|
+
* active planning or ui-design sessions and points the model at
|
|
101
|
+
* `planning_ask` instead.
|
|
102
|
+
*
|
|
103
|
+
* This is the runtime complement to the prompt-level directive in the
|
|
104
|
+
* planning and ui-design prompts: even if prompt wording drifts or the
|
|
105
|
+
* model ignores it, invoking `ask` in those modes returns a block result
|
|
106
|
+
* with a truthful redirection.
|
|
107
|
+
*/
|
|
108
|
+
export function registerPlanningAskToolGuard(platform: Platform): void {
|
|
109
|
+
platform.on("tool_call", (event) => {
|
|
110
|
+
if (event.toolName !== "ask") return;
|
|
111
|
+
|
|
112
|
+
const reason = getAskRedirectReason();
|
|
113
|
+
if (!reason) return;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
block: true,
|
|
117
|
+
reason,
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/planning/render-markdown.ts
|
|
2
|
+
//
|
|
3
|
+
// Deterministic PlanSpec → markdown renderer. Every saved plan comes from
|
|
4
|
+
// this module, so the on-disk representation cannot drift from the
|
|
5
|
+
// canonical PlanSpec. Parsers in src/storage/plans.ts must be able to
|
|
6
|
+
// recover a valid PlanSpec from this output — covered by the round-trip
|
|
7
|
+
// test in tests/planning/render-markdown.test.ts.
|
|
8
|
+
|
|
9
|
+
import type { PlanSpec, PlanSpecTask } from "./spec.js";
|
|
10
|
+
|
|
11
|
+
function renderFrontmatter(spec: PlanSpec): string {
|
|
12
|
+
const lines = ["---"];
|
|
13
|
+
lines.push(`name: ${spec.name}`);
|
|
14
|
+
if (spec.created) lines.push(`created: ${spec.created}`);
|
|
15
|
+
lines.push(`tags: [${spec.tags.join(", ")}]`);
|
|
16
|
+
lines.push("---");
|
|
17
|
+
return lines.join("\n");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function renderContextSection(context: string): string {
|
|
21
|
+
const body = context.trim();
|
|
22
|
+
return body.length > 0 ? `## Context\n\n${body}` : "## Context\n";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function renderFilesList(files: string[]): string {
|
|
26
|
+
if (files.length === 0) return "**files**: (none)";
|
|
27
|
+
return files.map((file) => `- \`${file}\``).join("\n");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderTask(task: PlanSpecTask): string {
|
|
31
|
+
const header = `### Task ${task.id}: ${task.name}${task.model ? ` [model: ${task.model}]` : ""}`;
|
|
32
|
+
const parts: string[] = [header, ""];
|
|
33
|
+
|
|
34
|
+
parts.push("**files**:");
|
|
35
|
+
parts.push(renderFilesList(task.files));
|
|
36
|
+
parts.push("");
|
|
37
|
+
|
|
38
|
+
parts.push(`**criteria**: ${task.criteria}`);
|
|
39
|
+
parts.push(`**complexity**: ${task.complexity}`);
|
|
40
|
+
|
|
41
|
+
if (task.description && task.description.trim() !== task.name.trim()) {
|
|
42
|
+
parts.push("");
|
|
43
|
+
parts.push(task.description.trim());
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return parts.join("\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render a validated PlanSpec to the canonical markdown representation
|
|
51
|
+
* accepted by src/storage/plans.ts's parser. Output is stable across
|
|
52
|
+
* identical input so diffs stay reviewable.
|
|
53
|
+
*/
|
|
54
|
+
export function renderPlanSpec(spec: PlanSpec): string {
|
|
55
|
+
const sections: string[] = [
|
|
56
|
+
renderFrontmatter(spec),
|
|
57
|
+
"",
|
|
58
|
+
`# ${spec.name}`,
|
|
59
|
+
"",
|
|
60
|
+
renderContextSection(spec.context),
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
if (spec.tasks.length > 0) {
|
|
64
|
+
sections.push("");
|
|
65
|
+
sections.push("## Tasks");
|
|
66
|
+
for (const task of spec.tasks) {
|
|
67
|
+
sections.push("");
|
|
68
|
+
sections.push(renderTask(task));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Trailing newline for POSIX hygiene.
|
|
73
|
+
return sections.join("\n") + "\n";
|
|
74
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/planning/spec.ts
|
|
2
|
+
//
|
|
3
|
+
// Canonical TypeBox contract for a supipowers implementation plan (PlanSpec).
|
|
4
|
+
// The agent produces markdown, src/storage/plans.ts parses it, and this
|
|
5
|
+
// schema is the validator of record. Markdown that doesn't parse into a
|
|
6
|
+
// PlanSpec is rejected — no silent promotion of partial artifacts.
|
|
7
|
+
//
|
|
8
|
+
// Phase 3 exit gate: PlanSpec is the canonical planning artifact; markdown
|
|
9
|
+
// is rendered from it via render-markdown.ts, not the other way around.
|
|
10
|
+
|
|
11
|
+
import { Type, type Static } from "@sinclair/typebox";
|
|
12
|
+
|
|
13
|
+
export const TASK_COMPLEXITY_VALUES = ["small", "medium", "large"] as const;
|
|
14
|
+
|
|
15
|
+
export const PlanSpecTaskSchema = Type.Object(
|
|
16
|
+
{
|
|
17
|
+
id: Type.Integer({ minimum: 1 }),
|
|
18
|
+
name: Type.String({ minLength: 1 }),
|
|
19
|
+
description: Type.String(),
|
|
20
|
+
files: Type.Array(Type.String({ minLength: 1 })),
|
|
21
|
+
criteria: Type.String(),
|
|
22
|
+
complexity: Type.Union(TASK_COMPLEXITY_VALUES.map((v) => Type.Literal(v))),
|
|
23
|
+
model: Type.Optional(Type.String({ minLength: 1 })),
|
|
24
|
+
},
|
|
25
|
+
{ additionalProperties: false },
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const PlanSpecSchema = Type.Object(
|
|
29
|
+
{
|
|
30
|
+
name: Type.String({ minLength: 1 }),
|
|
31
|
+
/** ISO date string, e.g. "2026-04-17". Empty string is tolerated for
|
|
32
|
+
* legacy plans produced before the field was required. */
|
|
33
|
+
created: Type.String(),
|
|
34
|
+
tags: Type.Array(Type.String()),
|
|
35
|
+
context: Type.String(),
|
|
36
|
+
tasks: Type.Array(PlanSpecTaskSchema),
|
|
37
|
+
},
|
|
38
|
+
{ additionalProperties: false },
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export type PlanSpec = Static<typeof PlanSpecSchema>;
|
|
42
|
+
export type PlanSpecTask = Static<typeof PlanSpecTaskSchema>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Platform } from "../platform/types.js";
|
|
2
|
+
import { systemPromptText } from "../platform/system-prompt.js";
|
|
2
3
|
import { createDebugLogger } from "../debug/logger.js";
|
|
3
4
|
import { getPlanningDebugLogger, getPlanningPromptOptions, isPlanningActive } from "./approval-flow.js";
|
|
4
5
|
import { buildPlanWriterPrompt } from "./plan-writer-prompt.js";
|
|
@@ -8,6 +9,9 @@ import { buildSpecReviewerPrompt } from "./spec-reviewer.js";
|
|
|
8
9
|
export interface PlanningSystemPromptOptions {
|
|
9
10
|
skillContent?: string;
|
|
10
11
|
dotDirDisplay: string;
|
|
12
|
+
/** Absolute path to the directory where plans must be saved.
|
|
13
|
+
* Owned by approval-flow's listPlans watcher; the agent MUST write here. */
|
|
14
|
+
plansDir: string;
|
|
11
15
|
topic?: string;
|
|
12
16
|
isQuick?: boolean;
|
|
13
17
|
}
|
|
@@ -81,7 +85,7 @@ function buildFullPlanningSection(options: PlanningSystemPromptOptions): string
|
|
|
81
85
|
const specReviewerPrompt = buildSpecReviewerPrompt("<path-to-spec-file>");
|
|
82
86
|
const planWriterPrompt = buildPlanWriterPrompt({
|
|
83
87
|
specPath: "<path-to-approved-spec>",
|
|
84
|
-
|
|
88
|
+
plansDir: options.plansDir,
|
|
85
89
|
});
|
|
86
90
|
|
|
87
91
|
return [
|
|
@@ -97,10 +101,9 @@ function buildFullPlanningSection(options: PlanningSystemPromptOptions): string
|
|
|
97
101
|
"## HARD-GATE",
|
|
98
102
|
"",
|
|
99
103
|
"- Do NOT write production code, apply patches, or claim that you changed files during planning.",
|
|
100
|
-
|
|
104
|
+
`- The only allowed file writes are the approved design doc under \`${options.dotDirDisplay}/supipowers/specs/\` and the final implementation plan under \`${options.plansDir}/\`.`,
|
|
101
105
|
"- Keep planning artifacts local. Do NOT stage, commit, or push the design doc or implementation plan.",
|
|
102
106
|
"- If the user asks to jump into coding early, explain that planning mode must finish first.",
|
|
103
|
-
"- When you need to ask the user a question with options, use the `planning_ask` tool instead of `ask`. It has no timeout so the user can think without pressure.",
|
|
104
107
|
"",
|
|
105
108
|
"## Planning Workflow",
|
|
106
109
|
"",
|
|
@@ -131,7 +134,7 @@ function buildFullPlanningSection(options: PlanningSystemPromptOptions): string
|
|
|
131
134
|
"- Apply YAGNI ruthlessly and prefer isolated units with clear boundaries.",
|
|
132
135
|
"",
|
|
133
136
|
"### Phase 5: Write the design doc",
|
|
134
|
-
|
|
137
|
+
`- After the user approves the design, write \`${options.dotDirDisplay}/supipowers/specs/YYYY-MM-DD-<topic>-design.md\`.`,
|
|
135
138
|
"- Use concise, implementation-ready language.",
|
|
136
139
|
"- Keep the design doc local; do NOT commit it to git.",
|
|
137
140
|
"",
|
|
@@ -209,7 +212,7 @@ function buildQuickPlanningSection(options: PlanningSystemPromptOptions): string
|
|
|
209
212
|
"",
|
|
210
213
|
"4. Each task must include name, files, criteria, and complexity (small/medium/large).",
|
|
211
214
|
` - ${QUICK_PLAN_TASK_CONTENT_REQUIREMENT}`,
|
|
212
|
-
|
|
215
|
+
`5. Save the plan under \`${options.plansDir}/YYYY-MM-DD-<feature-name>.md\`.`,
|
|
213
216
|
"6. After saving, tell the user: `Plan saved to <path>. Review it and approve when ready.`",
|
|
214
217
|
"7. Then stop and wait. The approval UI handles execution handoff.",
|
|
215
218
|
...buildAdditionalGuidelines(options.skillContent),
|
|
@@ -232,7 +235,7 @@ function buildPlanningCriticalBlock(options: PlanningSystemPromptOptions): strin
|
|
|
232
235
|
"This is NOT native OMP plan mode.",
|
|
233
236
|
"You **MUST NOT** call `exit_plan_mode` or `ExitPlanMode` — it will fail.",
|
|
234
237
|
`You **MUST NOT** write plans to \`local://PLAN.md\` — that is OMP's native plan location and will not trigger the approval flow.`,
|
|
235
|
-
`You **MUST** save the plan to \`${options.
|
|
238
|
+
`You **MUST** save the plan to \`${options.plansDir}/YYYY-MM-DD-<feature-name>.md\` using the Write tool.`,
|
|
236
239
|
"After saving, tell the user the plan path, then **stop and yield your turn**.",
|
|
237
240
|
"The approval UI appears automatically when your turn ends and a new plan file is detected in that directory.",
|
|
238
241
|
"</critical>",
|
|
@@ -270,7 +273,7 @@ export function registerPlanningSystemPromptHook(platform: Platform): void {
|
|
|
270
273
|
|
|
271
274
|
const debugLogger = getPlanningDebugLogger() ?? createDebugLogger(platform.paths, ctx as any, "plan");
|
|
272
275
|
const options = getPlanningPromptOptions();
|
|
273
|
-
const basePrompt = (event as any).systemPrompt
|
|
276
|
+
const basePrompt = systemPromptText((event as any).systemPrompt);
|
|
274
277
|
if (!options || !basePrompt) {
|
|
275
278
|
debugLogger.log("system_prompt_override_skipped", {
|
|
276
279
|
hasPlanningOptions: Boolean(options),
|
|
@@ -288,6 +291,6 @@ export function registerPlanningSystemPromptHook(platform: Platform): void {
|
|
|
288
291
|
systemPrompt,
|
|
289
292
|
});
|
|
290
293
|
|
|
291
|
-
return { systemPrompt };
|
|
294
|
+
return { systemPrompt: [systemPrompt] };
|
|
292
295
|
});
|
|
293
296
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/planning/validate.ts
|
|
2
|
+
//
|
|
3
|
+
// Typed validation helpers for PlanSpec. Consumers (plan command, approval
|
|
4
|
+
// flow) call `validatePlanSpec(data)` and branch on `.output` vs `.error`.
|
|
5
|
+
// Everyone converges on the same path so validation errors surface in one
|
|
6
|
+
// consistent shape with field-level paths.
|
|
7
|
+
|
|
8
|
+
import { Value } from "@sinclair/typebox/value";
|
|
9
|
+
import { collectValidationErrors, formatValidationErrors } from "../ai/structured-output.js";
|
|
10
|
+
import type { ValidationError } from "../types.js";
|
|
11
|
+
import { PlanSpecSchema, type PlanSpec } from "./spec.js";
|
|
12
|
+
|
|
13
|
+
export interface PlanSpecValidationResult {
|
|
14
|
+
output: PlanSpec | null;
|
|
15
|
+
error: string | null;
|
|
16
|
+
errors: ValidationError[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Validate an arbitrary value against PlanSpecSchema. Returns `{output, null}`
|
|
21
|
+
* on success and `{null, error, errors}` on failure, where `error` is a
|
|
22
|
+
* human-readable summary and `errors` lists every field-level issue.
|
|
23
|
+
*/
|
|
24
|
+
export function validatePlanSpec(data: unknown): PlanSpecValidationResult {
|
|
25
|
+
if (Value.Check(PlanSpecSchema, data)) {
|
|
26
|
+
return { output: data as PlanSpec, error: null, errors: [] };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const errors = collectValidationErrors(PlanSpecSchema, data);
|
|
30
|
+
const error = errors.length > 0
|
|
31
|
+
? formatValidationErrors(errors).join("; ")
|
|
32
|
+
: "Plan does not match the PlanSpec schema.";
|
|
33
|
+
return { output: null, error, errors };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Narrowing predicate for PlanSpec. Use when you do not need error detail.
|
|
38
|
+
*/
|
|
39
|
+
export function isPlanSpec(value: unknown): value is PlanSpec {
|
|
40
|
+
return Value.Check(PlanSpecSchema, value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Markdown-level convenience
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
import { parsePlan } from "../storage/plans.js";
|
|
49
|
+
import { renderPlanSpec } from "./render-markdown.js";
|
|
50
|
+
|
|
51
|
+
export interface PlanMarkdownValidationResult extends PlanSpecValidationResult {
|
|
52
|
+
/** Canonical markdown rendered from the validated PlanSpec. Null on failure. */
|
|
53
|
+
canonicalMarkdown: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse a plan markdown document, project it into a PlanSpec candidate,
|
|
58
|
+
* validate against PlanSpecSchema, and render the canonical markdown for a
|
|
59
|
+
* validated spec. On failure, returns `canonicalMarkdown: null` alongside
|
|
60
|
+
* the usual error and errors fields.
|
|
61
|
+
*/
|
|
62
|
+
export function validatePlanMarkdown(content: string, planFileName: string = ""): PlanMarkdownValidationResult {
|
|
63
|
+
const parsed = parsePlan(content, planFileName);
|
|
64
|
+
const candidate = {
|
|
65
|
+
name: parsed.name,
|
|
66
|
+
created: parsed.created,
|
|
67
|
+
tags: parsed.tags,
|
|
68
|
+
context: parsed.context,
|
|
69
|
+
tasks: parsed.tasks.map((t) => ({
|
|
70
|
+
id: t.id,
|
|
71
|
+
name: t.name,
|
|
72
|
+
description: t.description,
|
|
73
|
+
files: t.files,
|
|
74
|
+
criteria: t.criteria,
|
|
75
|
+
complexity: t.complexity,
|
|
76
|
+
...(t.model ? { model: t.model } : {}),
|
|
77
|
+
})),
|
|
78
|
+
};
|
|
79
|
+
const validated = validatePlanSpec(candidate);
|
|
80
|
+
return {
|
|
81
|
+
...validated,
|
|
82
|
+
canonicalMarkdown: validated.output ? renderPlanSpec(validated.output) : null,
|
|
83
|
+
};
|
|
84
|
+
}
|