@pareto4/pp-core 1.11.1 → 1.12.0
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/.claude-plugin/plugin.json +1 -1
- package/README.md +31 -0
- package/agents/pp-code-fixer.md +1 -1
- package/agents/pp-debug-session-manager.md +1 -1
- package/agents/pp-debugger.md +1 -1
- package/agents/pp-executor.md +20 -10
- package/agents/pp-intel-updater.md +1 -1
- package/agents/pp-phase-researcher.md +1 -1
- package/agents/pp-plan-checker.md +5 -5
- package/agents/pp-planner.md +10 -24
- package/agents/pp-project-researcher.md +1 -1
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-researcher.md +1 -1
- package/agents/pp-verifier.md +8 -4
- package/bin/install.js +429 -82
- package/commands/pp-autonomous.md +8 -6
- package/commands/pp-discovery-to-build.md +1 -1
- package/commands/pp-discovery.md +3 -2
- package/commands/pp-discuss-phase.md +2 -2
- package/commands/pp-execute-phase.md +3 -1
- package/commands/pp-graphify.md +5 -5
- package/commands/pp-import.md +1 -1
- package/commands/pp-map-codebase.md +1 -1
- package/commands/pp-ns-project.md +2 -3
- package/commands/pp-ns-workflow.md +1 -1
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-verify-work.md +2 -1
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-cursor-session-start.js +2 -2
- package/hooks/dist/pp-session-state.sh +1 -1
- package/hooks/dist/pp-statusline.js +21 -2
- package/hooks/pp-cursor-session-start.js +2 -2
- package/hooks/pp-session-state.sh +1 -1
- package/hooks/pp-statusline.js +21 -2
- package/package.json +18 -5
- package/pareto-skills/pp-project-setup/SKILL.md +5 -63
- package/pp-core/bin/detect-build-commands.sh +182 -0
- package/pp-core/bin/lib/capability-registry.cjs +65 -7
- package/pp-core/bin/lib/check-command-router.cjs +79 -38
- package/pp-core/bin/lib/clusters.cjs +2 -3
- package/pp-core/bin/lib/command-aliases.cjs +36 -0
- package/pp-core/bin/lib/command-arg-projection.cjs +99 -1
- package/pp-core/bin/lib/commands.cjs +21 -6
- package/pp-core/bin/lib/compose-bindings.cjs +70 -6
- package/pp-core/bin/lib/config-schema.cjs +16 -0
- package/pp-core/bin/lib/config.cjs +142 -27
- package/pp-core/bin/lib/core-utils.cjs +43 -7
- package/pp-core/bin/lib/discovery-input-check.cjs +218 -0
- package/pp-core/bin/lib/discovery-rebaseline.cjs +1372 -0
- package/pp-core/bin/lib/discovery-research-track.cjs +979 -0
- package/pp-core/bin/lib/discovery-rollup.cjs +476 -0
- package/pp-core/bin/lib/discovery-screens.cjs +1995 -0
- package/pp-core/bin/lib/discovery-sweep.cjs +390 -0
- package/pp-core/bin/lib/discovery-table.cjs +98 -0
- package/pp-core/bin/lib/dispatch-plan.cjs +472 -0
- package/pp-core/bin/lib/frontmatter.cjs +17 -2
- package/pp-core/bin/lib/init-command-router.cjs +8 -5
- package/pp-core/bin/lib/init-contract.cjs +93 -4
- package/pp-core/bin/lib/init.cjs +124 -134
- package/pp-core/bin/lib/install-profiles.cjs +5 -0
- package/pp-core/bin/lib/install-run.cjs +536 -0
- package/pp-core/bin/lib/io.cjs +15 -0
- package/pp-core/bin/lib/loop-resolver.cjs +24 -0
- package/pp-core/bin/lib/milestone.cjs +345 -48
- package/pp-core/bin/lib/phase-command-router.cjs +23 -1
- package/pp-core/bin/lib/phase-locator.cjs +15 -6
- package/pp-core/bin/lib/phase-scope.cjs +91 -0
- package/pp-core/bin/lib/phase-tier.cjs +467 -0
- package/pp-core/bin/lib/phase.cjs +247 -21
- package/pp-core/bin/lib/phases-command-router.cjs +2 -0
- package/pp-core/bin/lib/plan-graph.cjs +368 -0
- package/pp-core/bin/lib/planning-workspace.cjs +53 -0
- package/pp-core/bin/lib/platform-bindings.cjs +32 -2
- package/pp-core/bin/lib/profile-output.cjs +11 -3
- package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
- package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
- package/pp-core/bin/lib/roadmap.cjs +141 -39
- package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
- package/pp-core/bin/lib/state-command-router.cjs +82 -13
- package/pp-core/bin/lib/state-document.cjs +124 -7
- package/pp-core/bin/lib/state.cjs +784 -257
- package/pp-core/bin/lib/template.cjs +22 -6
- package/pp-core/bin/lib/vault-command-router.cjs +57 -6
- package/pp-core/bin/lib/vault-practices.cjs +6 -36
- package/pp-core/bin/lib/vault-write-back.cjs +20 -4
- package/pp-core/bin/lib/vault.cjs +555 -23
- package/pp-core/bin/lib/verify.cjs +53 -31
- package/pp-core/bin/lib/workstream.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +26 -15
- package/pp-core/bin/portable-timeout.sh +89 -0
- package/pp-core/bin/pp-tools.cjs +149 -9
- package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
- package/pp-core/bin/shared/config-schema.manifest.json +3 -0
- package/pp-core/references/autonomous-best-effort.md +461 -0
- package/pp-core/references/autonomous-lifecycle.md +146 -0
- package/pp-core/references/autonomous-smart-discuss.md +16 -12
- package/pp-core/references/checkpoints.md +1 -1
- package/pp-core/references/discovery-id-conventions.md +202 -0
- package/pp-core/references/discovery-rebaseline.md +254 -0
- package/pp-core/references/discovery-research-track.md +283 -0
- package/pp-core/references/discovery-traceability.md +400 -0
- package/pp-core/references/loop-hook-dispatch.md +7 -0
- package/pp-core/references/mandatory-initial-read.md +1 -1
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-human-verify-mode.md +1 -1
- package/pp-core/references/planner-load-graph-context.md +1 -1
- package/pp-core/references/planner-plan-graph.md +66 -0
- package/pp-core/references/planner-tdd.md +1 -0
- package/pp-core/references/planning-config.md +8 -0
- package/pp-core/references/sketch-tooling.md +4 -0
- package/pp-core/references/tdd.md +1 -0
- package/pp-core/templates/claude-md.md +1 -1
- package/pp-core/templates/phase-prompt.md +6 -2
- package/pp-core/templates/prototype-review/review.js +303 -0
- package/pp-core/templates/state.md +6 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-phase.md +1 -1
- package/pp-core/workflows/add-tests.md +1 -1
- package/pp-core/workflows/add-todo.md +1 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -2
- package/pp-core/workflows/audit-fix.md +12 -16
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/audit-uat.md +1 -1
- package/pp-core/workflows/autonomous.md +68 -306
- package/pp-core/workflows/check-todos.md +1 -1
- package/pp-core/workflows/cleanup.md +1 -1
- package/pp-core/workflows/code-review-fix.md +1 -1
- package/pp-core/workflows/code-review.md +19 -7
- package/pp-core/workflows/complete-milestone.md +41 -60
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
- package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
- package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
- package/pp-core/workflows/discovery-sprint.md +79 -1714
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
- package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
- package/pp-core/workflows/discovery-to-build.md +21 -238
- package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
- package/pp-core/workflows/discuss-phase.md +9 -2
- package/pp-core/workflows/do.md +4 -4
- package/pp-core/workflows/docs-update.md +1 -1
- package/pp-core/workflows/edit-phase.md +1 -1
- package/pp-core/workflows/eval-review.md +1 -1
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
- package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
- package/pp-core/workflows/execute-phase.md +102 -216
- package/pp-core/workflows/execute-plan.md +6 -2
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/extract-learnings.md +1 -1
- package/pp-core/workflows/forensics.md +4 -4
- package/pp-core/workflows/graduation.md +1 -1
- package/pp-core/workflows/health.md +1 -1
- package/pp-core/workflows/help/modes/brief.md +1 -0
- package/pp-core/workflows/help/modes/default.md +2 -1
- package/pp-core/workflows/help/modes/full.md +12 -5
- package/pp-core/workflows/import.md +1 -1
- package/pp-core/workflows/ingest-docs.md +1 -1
- package/pp-core/workflows/init.md +18 -2
- package/pp-core/workflows/insert-phase.md +1 -1
- package/pp-core/workflows/list-workspaces.md +1 -1
- package/pp-core/workflows/manager.md +3 -3
- package/pp-core/workflows/map-codebase.md +2 -2
- package/pp-core/workflows/milestone-summary.md +4 -4
- package/pp-core/workflows/mvp-phase.md +1 -1
- package/pp-core/workflows/new-milestone.md +7 -8
- package/pp-core/workflows/new-project.md +6 -1
- package/pp-core/workflows/new-workspace.md +1 -1
- package/pp-core/workflows/next.md +2 -2
- package/pp-core/workflows/note.md +2 -2
- package/pp-core/workflows/pause-work.md +1 -1
- package/pp-core/workflows/plan-milestone-gaps.md +1 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
- package/pp-core/workflows/plan-phase.md +43 -167
- package/pp-core/workflows/plan-review-convergence.md +2 -2
- package/pp-core/workflows/plant-seed.md +1 -1
- package/pp-core/workflows/pr-branch.md +1 -1
- package/pp-core/workflows/profile-user.md +1 -1
- package/pp-core/workflows/progress.md +3 -3
- package/pp-core/workflows/quick.md +3 -1
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/remove-workspace.md +1 -1
- package/pp-core/workflows/resume-project.md +6 -12
- package/pp-core/workflows/review.md +1 -1
- package/pp-core/workflows/scan.md +1 -1
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/settings-advanced.md +1 -1
- package/pp-core/workflows/settings-integrations.md +1 -1
- package/pp-core/workflows/settings.md +1 -1
- package/pp-core/workflows/ship.md +25 -8
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +2 -1
- package/pp-core/workflows/spec-phase.md +1 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +1 -1
- package/pp-core/workflows/stats.md +2 -2
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/transition.md +5 -9
- package/pp-core/workflows/ui-phase.md +23 -2
- package/pp-core/workflows/ui-review.md +1 -1
- package/pp-core/workflows/ultraplan-phase.md +2 -2
- package/pp-core/workflows/validate-phase.md +1 -1
- package/pp-core/workflows/vault-write-back.md +1 -1
- package/pp-core/workflows/verify-phase.md +29 -26
- package/pp-core/workflows/verify-work.md +14 -10
- package/scripts/base64-scan.sh +7 -41
- package/scripts/changeset/cli.cjs +25 -5
- package/scripts/dev-env.cjs +462 -0
- package/scripts/lib/allowlist-ratchet.cjs +103 -1
- package/scripts/lib/size-budget.cjs +311 -0
- package/scripts/lint-neutrality-names.cjs +694 -0
- package/scripts/lint-org-coordinates.allowlist.json +35 -0
- package/scripts/lint-org-coordinates.cjs +426 -0
- package/scripts/lint-skill-deps.cjs +48 -9
- package/scripts/lint-test-file-count.cjs +17 -2
- package/scripts/prompt-injection-scan.sh +41 -20
- package/scripts/sync-manifest-versions.cjs +41 -7
- package/scripts/sync-runtime-launcher.cjs +106 -1
- package/scripts/update-size-baseline.cjs +274 -30
- package/scripts/workflow-size.cjs +61 -9
- package/commands/pp-build-handoff.md +0 -46
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Dispatch plan — the single owner of what execute-phase spawns and runs.
|
|
4
|
+
*
|
|
5
|
+
* From the plan inventory (`phase-plan-index`: waves and serial groups from
|
|
6
|
+
* src/plan-graph.cts) and the project's isolation settings, it computes, per
|
|
7
|
+
* wave and in order:
|
|
8
|
+
*
|
|
9
|
+
* - the executor spawns: ONE spawn per serial group, on the main checkout
|
|
10
|
+
* (no worktree), listing its plans in run order; one spawn per other plan,
|
|
11
|
+
* isolated in a worktree unless worktrees are off or the plan touches a
|
|
12
|
+
* git submodule path (the rule execute-phase applied per plan before);
|
|
13
|
+
* - whether each spawn needs a build: only when one of its plans touches a
|
|
14
|
+
* source file (`isSourceFile`, the plan-graph predicate);
|
|
15
|
+
* - the merge step, then exactly ONE full-suite run by the orchestrator after
|
|
16
|
+
* the merge, then exactly ONE STATE/ROADMAP/REQUIREMENTS tracking commit by
|
|
17
|
+
* the orchestrator;
|
|
18
|
+
* - planning defects: a source file shared by two plans of one wave, or any
|
|
19
|
+
* file shared by two plans in different spawns. A wave with a defect runs
|
|
20
|
+
* its spawns one at a time.
|
|
21
|
+
*
|
|
22
|
+
* Totals: spawn count, suite-run count, tracking-commit count, build count and
|
|
23
|
+
* the SUMMARY.md files the phase is expected to produce.
|
|
24
|
+
*
|
|
25
|
+
* There is no spawn trace: workflows are markdown a model follows. This plan
|
|
26
|
+
* is what execute-phase spawns ("spawn exactly the listed spawns"), so it is
|
|
27
|
+
* the dry run that tests count.
|
|
28
|
+
*
|
|
29
|
+
* ## Stages
|
|
30
|
+
*
|
|
31
|
+
* Output is shaped as `stages: { plan, execute, verify }`. `stages.plan` and
|
|
32
|
+
* `stages.verify` come from the phase's resolved ceremony tier
|
|
33
|
+
* (`resolvePhaseTier` in src/phase-tier.cts, laid out by `computeTierStages`
|
|
34
|
+
* here): researcher, pattern mapper,
|
|
35
|
+
* planner (always) and plan checker passes; then code reviewer and fixer,
|
|
36
|
+
* the heavy-tier install run (`phase.install-run`, src/install-run.cts),
|
|
37
|
+
* verifier, security auditor and nyquist auditor. `totals.spawns` counts every agent spawn of every stage, as an upper
|
|
38
|
+
* bound: entries marked `conditional` run only when the step before them asks
|
|
39
|
+
* for it (a revision after a failed check, a fix after review findings).
|
|
40
|
+
*/
|
|
41
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
42
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.STAGE_AGENTS = void 0;
|
|
46
|
+
exports.computeTierStages = computeTierStages;
|
|
47
|
+
exports.submoduleIntersection = submoduleIntersection;
|
|
48
|
+
exports.parseGitmodulesPaths = parseGitmodulesPaths;
|
|
49
|
+
exports.computeDispatchPlan = computeDispatchPlan;
|
|
50
|
+
exports.parseDispatchPlanArgs = parseDispatchPlanArgs;
|
|
51
|
+
exports.resolveDispatchPolicy = resolveDispatchPolicy;
|
|
52
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
53
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
54
|
+
const plan_graph_cjs_1 = require("./plan-graph.cjs");
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
56
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- config-loader.cjs is an export= CommonJS module
|
|
58
|
+
const configLoaderMod = require("./config-loader.cjs");
|
|
59
|
+
const { planningDir } = planningWorkspace;
|
|
60
|
+
const { loadConfig } = configLoaderMod;
|
|
61
|
+
/** Agent type names the stages spawn (each is a file under agents/). */
|
|
62
|
+
exports.STAGE_AGENTS = Object.freeze({
|
|
63
|
+
researcher: 'pp-phase-researcher',
|
|
64
|
+
patternMapper: 'pp-pattern-mapper',
|
|
65
|
+
planner: 'pp-planner',
|
|
66
|
+
planChecker: 'pp-plan-checker',
|
|
67
|
+
verifier: 'pp-verifier',
|
|
68
|
+
codeReviewer: 'pp-code-reviewer',
|
|
69
|
+
codeFixer: 'pp-code-fixer',
|
|
70
|
+
nyquistAuditor: 'pp-nyquist-auditor',
|
|
71
|
+
securityAuditor: 'pp-security-auditor',
|
|
72
|
+
});
|
|
73
|
+
function spawn(id, agent, gate, reason, conditional = false) {
|
|
74
|
+
return { id, kind: 'spawn', agent, gate, reason, conditional };
|
|
75
|
+
}
|
|
76
|
+
function step(id, gate, reason) {
|
|
77
|
+
return { id, kind: 'step', agent: null, gate, reason, conditional: false };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The planning and verification stages of a phase, from its resolved tier.
|
|
81
|
+
* `planBounceScript`: bounce runs only with a configured script.
|
|
82
|
+
*/
|
|
83
|
+
function computeTierStages(tier, opts = {}) {
|
|
84
|
+
const g = tier.gates;
|
|
85
|
+
const plan = [];
|
|
86
|
+
if (g.research)
|
|
87
|
+
plan.push(spawn('plan-research', exports.STAGE_AGENTS.researcher, 'research', 'workflow.research is on for this tier'));
|
|
88
|
+
if (g.pattern_mapper)
|
|
89
|
+
plan.push(spawn('plan-patterns', exports.STAGE_AGENTS.patternMapper, 'pattern_mapper', 'workflow.pattern_mapper is on for this tier'));
|
|
90
|
+
plan.push(spawn('plan-planner', exports.STAGE_AGENTS.planner, 'planner', 'the planner always runs'));
|
|
91
|
+
if (g.plan_check) {
|
|
92
|
+
for (let pass = 1; pass <= g.plan_check_passes; pass++) {
|
|
93
|
+
if (pass > 1) {
|
|
94
|
+
plan.push(spawn(`plan-revise-${pass - 1}`, exports.STAGE_AGENTS.planner, 'plan_check', `revision ${pass - 1}: only when check pass ${pass - 1} found issues`, true));
|
|
95
|
+
}
|
|
96
|
+
plan.push(spawn(`plan-check-${pass}`, exports.STAGE_AGENTS.planChecker, 'plan_check', pass === 1 ? `check pass 1 of at most ${g.plan_check_passes}` : `check pass ${pass} of at most ${g.plan_check_passes}: after revision ${pass - 1}`, pass > 1));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (g.plan_bounce && opts.planBounceScript) {
|
|
100
|
+
plan.push(step('plan-bounce', 'plan_bounce', `refine plans through workflow.plan_bounce_script, ${g.plan_bounce_passes} pass(es)`));
|
|
101
|
+
if (g.plan_check)
|
|
102
|
+
plan.push(spawn('plan-bounce-check', exports.STAGE_AGENTS.planChecker, 'plan_bounce', 're-check bounced plans; a plan that fails is restored', true));
|
|
103
|
+
}
|
|
104
|
+
if (g.post_planning_gaps)
|
|
105
|
+
plan.push(step('plan-gaps', 'post_planning_gaps', 'post-planning gap report (plan:post), non-blocking'));
|
|
106
|
+
const verify = [];
|
|
107
|
+
if (g.code_review) {
|
|
108
|
+
verify.push(spawn('verify-code-review', exports.STAGE_AGENTS.codeReviewer, 'code_review', 'code review of the phase source changes'));
|
|
109
|
+
verify.push(spawn('verify-code-fix', exports.STAGE_AGENTS.codeFixer, 'code_review', 'fix loop: only when the review finds issues', true));
|
|
110
|
+
}
|
|
111
|
+
// The install run goes first: the verifier judges the phase on its results file.
|
|
112
|
+
if (g.install_verification)
|
|
113
|
+
verify.push(step('verify-install', 'install_verification', 'pack, install into a temp project, init and run the phase install checks against the installed copy (phase.install-run → NN-INSTALL-RUN.json)'));
|
|
114
|
+
if (g.verifier)
|
|
115
|
+
verify.push(spawn('verify-goal', exports.STAGE_AGENTS.verifier, 'verifier', 'goal-backward phase verification (VERIFICATION.md)'));
|
|
116
|
+
if (g.security_enforcement)
|
|
117
|
+
verify.push(spawn('verify-security', exports.STAGE_AGENTS.securityAuditor, 'security_enforcement', 'threat-model verification (SECURITY.md)'));
|
|
118
|
+
if (g.nyquist_validation)
|
|
119
|
+
verify.push(spawn('verify-nyquist', exports.STAGE_AGENTS.nyquistAuditor, 'nyquist_validation', 'validation-gap audit (VALIDATION.md)'));
|
|
120
|
+
return { plan, verify };
|
|
121
|
+
}
|
|
122
|
+
function normalizePath(file) {
|
|
123
|
+
return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
124
|
+
}
|
|
125
|
+
function stripEnds(p) {
|
|
126
|
+
return p.replace(/^\.\//, '').replace(/\/$/, '');
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Planned paths that touch a submodule (the per-plan worktree rule, #2772):
|
|
130
|
+
* either side normalised (leading `./`, one trailing `/`), matched both ways
|
|
131
|
+
* (planned path inside the submodule, or submodule inside the planned path),
|
|
132
|
+
* and a globbed planned path matched by its literal prefix before the first
|
|
133
|
+
* `*`, `?` or `[`.
|
|
134
|
+
*/
|
|
135
|
+
function submoduleIntersection(planFiles, submodulePaths) {
|
|
136
|
+
const hits = new Set();
|
|
137
|
+
const inside = (a, b) => a === b || a.startsWith(`${b}/`);
|
|
138
|
+
for (const smRaw of submodulePaths) {
|
|
139
|
+
const sm = stripEnds(smRaw.trim());
|
|
140
|
+
if (sm === '')
|
|
141
|
+
continue;
|
|
142
|
+
for (const pfRaw of planFiles) {
|
|
143
|
+
const pf = stripEnds(String(pfRaw).trim());
|
|
144
|
+
if (pf === '')
|
|
145
|
+
continue;
|
|
146
|
+
let matched = inside(pf, sm) || inside(sm, pf);
|
|
147
|
+
if (!matched && /[*?[]/.test(pf)) {
|
|
148
|
+
const prefix = pf.slice(0, pf.search(/[*?[]/)).replace(/\/$/, '');
|
|
149
|
+
matched = prefix !== '' && (inside(sm, prefix) || inside(prefix, sm));
|
|
150
|
+
}
|
|
151
|
+
if (matched)
|
|
152
|
+
hits.add(String(pfRaw).trim());
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return [...hits];
|
|
156
|
+
}
|
|
157
|
+
/** Submodule paths declared in a `.gitmodules` file's text. */
|
|
158
|
+
function parseGitmodulesPaths(text) {
|
|
159
|
+
const paths = [];
|
|
160
|
+
let inSubmodule = false;
|
|
161
|
+
for (const line of text.split(/\r?\n/)) {
|
|
162
|
+
const section = /^\s*\[\s*([^\s\]]+)/.exec(line);
|
|
163
|
+
if (section) {
|
|
164
|
+
inSubmodule = section[1].toLowerCase() === 'submodule';
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const kv = /^\s*path\s*=\s*(.+?)\s*$/i.exec(line);
|
|
168
|
+
if (inSubmodule && kv)
|
|
169
|
+
paths.push(kv[1].replace(/^"(.*)"$/, '$1'));
|
|
170
|
+
}
|
|
171
|
+
return paths;
|
|
172
|
+
}
|
|
173
|
+
function isolationFor(plan, policy) {
|
|
174
|
+
if (!policy.useWorktrees) {
|
|
175
|
+
return { isolation: 'main-checkout', reason: `worktrees off (workflow.use_worktrees=false, from ${policy.useWorktreesSource})` };
|
|
176
|
+
}
|
|
177
|
+
if (policy.submodulePaths.length > 0) {
|
|
178
|
+
const files = plan.files_modified.map((f) => String(f).trim()).filter((f) => f !== '');
|
|
179
|
+
if (files.length === 0) {
|
|
180
|
+
return { isolation: 'main-checkout', reason: 'files_modified missing/unparseable in a submodule project — no worktree, as a safety fallback' };
|
|
181
|
+
}
|
|
182
|
+
const hits = submoduleIntersection(files, policy.submodulePaths);
|
|
183
|
+
if (hits.length > 0) {
|
|
184
|
+
return { isolation: 'main-checkout', reason: `planned paths intersect submodule paths (${hits.join(' ')})` };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { isolation: 'worktree', reason: 'independent plan: its own worktree' };
|
|
188
|
+
}
|
|
189
|
+
function summaryPath(phaseDir, id) {
|
|
190
|
+
return `${phaseDir}/${id === '' ? '' : `${id}-`}SUMMARY.md`;
|
|
191
|
+
}
|
|
192
|
+
function planEntry(plan, phaseDir) {
|
|
193
|
+
const files = plan.files_modified.map(normalizePath).filter((f) => f !== '');
|
|
194
|
+
return {
|
|
195
|
+
id: plan.id,
|
|
196
|
+
title: plan.title,
|
|
197
|
+
build: files.length === 0 || files.some(plan_graph_cjs_1.isSourceFile),
|
|
198
|
+
touched_tests: files.filter((f) => (0, plan_graph_cjs_1.classifyPlanFile)(f) === 'test'),
|
|
199
|
+
summary: summaryPath(phaseDir, plan.id),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function buildReason(plans) {
|
|
203
|
+
const reasons = [];
|
|
204
|
+
for (const p of plans) {
|
|
205
|
+
const files = p.files_modified.map(normalizePath).filter((f) => f !== '');
|
|
206
|
+
if (files.length === 0)
|
|
207
|
+
reasons.push(`${p.id}: files_modified not declared`);
|
|
208
|
+
else {
|
|
209
|
+
const src = files.filter(plan_graph_cjs_1.isSourceFile);
|
|
210
|
+
if (src.length > 0)
|
|
211
|
+
reasons.push(`${p.id} touches ${src.join(', ')}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return reasons.length > 0 ? reasons.join('; ') : 'no plan touches a source file';
|
|
215
|
+
}
|
|
216
|
+
function waveDefects(spawns, byId) {
|
|
217
|
+
const members = [];
|
|
218
|
+
for (const s of spawns) {
|
|
219
|
+
for (const p of s.plans) {
|
|
220
|
+
const plan = byId.get(p.id);
|
|
221
|
+
members.push({ id: p.id, spawn: s.id, files: new Set(plan.files_modified.map(normalizePath).filter((f) => f !== '')) });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const defects = [];
|
|
225
|
+
for (let i = 0; i < members.length; i++) {
|
|
226
|
+
for (let j = i + 1; j < members.length; j++) {
|
|
227
|
+
const a = members[i];
|
|
228
|
+
const b = members[j];
|
|
229
|
+
const shared = [...a.files].filter((f) => b.files.has(f)).sort();
|
|
230
|
+
if (shared.length === 0)
|
|
231
|
+
continue;
|
|
232
|
+
const source = shared.filter(plan_graph_cjs_1.isSourceFile);
|
|
233
|
+
if (source.length > 0) {
|
|
234
|
+
defects.push({
|
|
235
|
+
kind: 'source-file-overlap',
|
|
236
|
+
plans: [a.id, b.id],
|
|
237
|
+
files: source,
|
|
238
|
+
message: `Plan ${a.id} and Plan ${b.id} both modify source file(s) ${source.join(', ')} in one wave — a shared source file must order the plans into separate waves (planning defect).`,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
else if (a.spawn !== b.spawn) {
|
|
242
|
+
defects.push({
|
|
243
|
+
kind: 'cross-spawn-overlap',
|
|
244
|
+
plans: [a.id, b.id],
|
|
245
|
+
files: shared,
|
|
246
|
+
message: `Plan ${a.id} and Plan ${b.id} both modify ${shared.join(', ')} but run in different spawns — they belong in one serial group (planning defect).`,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return defects;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Compute the dispatch plan. `plans` are `phase-plan-index` rows in plan file
|
|
255
|
+
* order; `phaseDir` is the phase directory relative to the project root.
|
|
256
|
+
*/
|
|
257
|
+
function computeDispatchPlan(phase, phaseDir, plans, policy, tier = null, tierOpts = {}) {
|
|
258
|
+
const byId = new Map(plans.map((p) => [p.id, p]));
|
|
259
|
+
const skippedComplete = plans.filter((p) => p.has_summary).map((p) => p.id);
|
|
260
|
+
const incomplete = plans.filter((p) => !p.has_summary && (!policy.gapsOnly || p.gap_closure === true));
|
|
261
|
+
const selected = incomplete.filter((p) => policy.waveFilter === null || p.wave === policy.waveFilter);
|
|
262
|
+
const selectedIds = new Set(selected.map((p) => p.id));
|
|
263
|
+
const skippedByFilter = plans.filter((p) => !p.has_summary && !selectedIds.has(p.id)).map((p) => p.id);
|
|
264
|
+
const lowerWaveIncomplete = policy.waveFilter === null
|
|
265
|
+
? []
|
|
266
|
+
: incomplete.filter((p) => p.wave < policy.waveFilter).map((p) => p.id);
|
|
267
|
+
const waveNumbers = [...new Set(selected.map((p) => p.wave))].sort((a, b) => a - b);
|
|
268
|
+
const waves = [];
|
|
269
|
+
for (const n of waveNumbers) {
|
|
270
|
+
const inWave = selected.filter((p) => p.wave === n);
|
|
271
|
+
const groupCounts = new Map();
|
|
272
|
+
for (const p of inWave)
|
|
273
|
+
if (p.serial_group)
|
|
274
|
+
groupCounts.set(p.serial_group, (groupCounts.get(p.serial_group) ?? 0) + 1);
|
|
275
|
+
const spawns = [];
|
|
276
|
+
const placedGroups = new Set();
|
|
277
|
+
for (const p of inWave) {
|
|
278
|
+
const group = p.serial_group && (groupCounts.get(p.serial_group) ?? 0) >= 2 ? p.serial_group : null;
|
|
279
|
+
if (group !== null) {
|
|
280
|
+
if (placedGroups.has(group))
|
|
281
|
+
continue;
|
|
282
|
+
placedGroups.add(group);
|
|
283
|
+
const members = inWave.filter((q) => q.serial_group === group);
|
|
284
|
+
const entries = members.map((q) => planEntry(q, phaseDir));
|
|
285
|
+
spawns.push({
|
|
286
|
+
id: `w${n}-s${spawns.length + 1}`,
|
|
287
|
+
agent: 'pp-executor',
|
|
288
|
+
kind: 'serial-group',
|
|
289
|
+
serial_group: group,
|
|
290
|
+
plans: entries,
|
|
291
|
+
isolation: 'main-checkout',
|
|
292
|
+
isolation_reason: `serial group ${group}: one executor runs its ${members.length} plans in order on the main checkout`,
|
|
293
|
+
build: entries.some((e) => e.build),
|
|
294
|
+
build_reason: buildReason(members),
|
|
295
|
+
checkpoint: members.some((q) => !q.autonomous),
|
|
296
|
+
expected_summaries: entries.map((e) => e.summary),
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const entry = planEntry(p, phaseDir);
|
|
301
|
+
const iso = isolationFor(p, policy);
|
|
302
|
+
spawns.push({
|
|
303
|
+
id: `w${n}-s${spawns.length + 1}`,
|
|
304
|
+
agent: 'pp-executor',
|
|
305
|
+
kind: 'plan',
|
|
306
|
+
serial_group: null,
|
|
307
|
+
plans: [entry],
|
|
308
|
+
isolation: iso.isolation,
|
|
309
|
+
isolation_reason: iso.reason,
|
|
310
|
+
build: entry.build,
|
|
311
|
+
build_reason: buildReason([p]),
|
|
312
|
+
checkpoint: !p.autonomous,
|
|
313
|
+
expected_summaries: [entry.summary],
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const defects = waveDefects(spawns, byId);
|
|
317
|
+
const worktreeSpawns = spawns.filter((s) => s.isolation === 'worktree').length;
|
|
318
|
+
const run = defects.length > 0 || !policy.parallelization || spawns.length < 2 ? 'sequential' : 'parallel';
|
|
319
|
+
const runReason = defects.length > 0
|
|
320
|
+
? 'planning defect: overlapping files_modified in this wave — run every spawn one at a time'
|
|
321
|
+
: !policy.parallelization
|
|
322
|
+
? 'parallelization is off'
|
|
323
|
+
: spawns.length < 2
|
|
324
|
+
? 'one spawn'
|
|
325
|
+
: 'worktree spawns run alongside each other; main-checkout spawns run one at a time';
|
|
326
|
+
const requirements = [...new Set(inWave.flatMap((p) => p.requirements ?? []))];
|
|
327
|
+
const expected = spawns.flatMap((s) => s.expected_summaries);
|
|
328
|
+
waves.push({
|
|
329
|
+
wave: n,
|
|
330
|
+
run,
|
|
331
|
+
run_reason: runReason,
|
|
332
|
+
spawns,
|
|
333
|
+
merge: { worktree_spawns: worktreeSpawns, cleanup: worktreeSpawns > 0 },
|
|
334
|
+
build: spawns.some((s) => s.build),
|
|
335
|
+
suite_runs: 1,
|
|
336
|
+
suite: { by: 'orchestrator', after: 'merge' },
|
|
337
|
+
tracking_commits: 1,
|
|
338
|
+
tracking: { by: 'orchestrator', plans: inWave.map((p) => p.id), requirements },
|
|
339
|
+
expected_summaries: expected,
|
|
340
|
+
defects,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const stages = tier ? computeTierStages(tier, tierOpts) : { plan: [], verify: [] };
|
|
344
|
+
const stagePlan = stages.plan;
|
|
345
|
+
const stageVerify = stages.verify;
|
|
346
|
+
const planSpawns = stagePlan.filter((e) => e.kind === 'spawn').length;
|
|
347
|
+
const verifySpawns = stageVerify.filter((e) => e.kind === 'spawn').length;
|
|
348
|
+
const executorSpawns = waves.flatMap((w) => w.spawns);
|
|
349
|
+
return {
|
|
350
|
+
phase,
|
|
351
|
+
phase_dir: phaseDir,
|
|
352
|
+
tier: tier ? { tier: tier.tier, source: tier.source, suppressed: tier.suppressed, gates: tier.gates } : null,
|
|
353
|
+
policy: {
|
|
354
|
+
use_worktrees: policy.useWorktrees,
|
|
355
|
+
use_worktrees_source: policy.useWorktreesSource,
|
|
356
|
+
parallelization: policy.parallelization,
|
|
357
|
+
submodule_paths: policy.submodulePaths,
|
|
358
|
+
},
|
|
359
|
+
filters: {
|
|
360
|
+
wave: policy.waveFilter,
|
|
361
|
+
gaps_only: policy.gapsOnly,
|
|
362
|
+
skipped_complete: skippedComplete,
|
|
363
|
+
skipped_by_filter: skippedByFilter,
|
|
364
|
+
lower_wave_incomplete: lowerWaveIncomplete,
|
|
365
|
+
},
|
|
366
|
+
stages: { plan: stagePlan, execute: { waves }, verify: stageVerify },
|
|
367
|
+
totals: {
|
|
368
|
+
waves: waves.length,
|
|
369
|
+
spawns: planSpawns + executorSpawns.length + verifySpawns,
|
|
370
|
+
plan_spawns: planSpawns,
|
|
371
|
+
verify_spawns: verifySpawns,
|
|
372
|
+
executor_spawns: executorSpawns.length,
|
|
373
|
+
main_checkout_spawns: executorSpawns.filter((s) => s.isolation === 'main-checkout').length,
|
|
374
|
+
isolated_spawns: executorSpawns.filter((s) => s.isolation === 'worktree').length,
|
|
375
|
+
builds: executorSpawns.filter((s) => s.build).length,
|
|
376
|
+
suite_runs: waves.reduce((sum, w) => sum + w.suite_runs, 0),
|
|
377
|
+
tracking_commits: waves.reduce((sum, w) => sum + w.tracking_commits, 0),
|
|
378
|
+
expected_summaries: waves.flatMap((w) => w.expected_summaries),
|
|
379
|
+
defects: waves.reduce((sum, w) => sum + w.defects.length, 0),
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
/** `<phase> [--wave N] [--gaps-only] [--use-worktrees true|false] [--tier light|standard|heavy]`. */
|
|
384
|
+
function parseDispatchPlanArgs(args) {
|
|
385
|
+
const usage = 'Usage: phase dispatch-plan <phase> [--wave N] [--gaps-only] [--use-worktrees true|false] [--tier light|standard|heavy]';
|
|
386
|
+
let phase = null;
|
|
387
|
+
let waveFilter = null;
|
|
388
|
+
let gapsOnly = false;
|
|
389
|
+
let useWorktrees = null;
|
|
390
|
+
let tier = null;
|
|
391
|
+
for (let i = 0; i < args.length; i++) {
|
|
392
|
+
const a = args[i];
|
|
393
|
+
if (a === '--raw')
|
|
394
|
+
continue;
|
|
395
|
+
if (a === '--gaps-only') {
|
|
396
|
+
gapsOnly = true;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (a === '--wave') {
|
|
400
|
+
const v = args[++i];
|
|
401
|
+
if (v === undefined || !/^\d+$/.test(v))
|
|
402
|
+
return { error: `--wave needs a wave number. ${usage}` };
|
|
403
|
+
waveFilter = parseInt(v, 10);
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
if (a === '--use-worktrees') {
|
|
407
|
+
const v = args[++i];
|
|
408
|
+
if (v !== 'true' && v !== 'false')
|
|
409
|
+
return { error: `--use-worktrees needs true or false. ${usage}` };
|
|
410
|
+
useWorktrees = v === 'true';
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (a === '--tier') {
|
|
414
|
+
const v = args[++i];
|
|
415
|
+
if (v !== 'light' && v !== 'standard' && v !== 'heavy')
|
|
416
|
+
return { error: `--tier needs light, standard or heavy. ${usage}` };
|
|
417
|
+
tier = v;
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (a.startsWith('--'))
|
|
421
|
+
return { error: `phase dispatch-plan does not support ${a}. ${usage}` };
|
|
422
|
+
if (phase !== null)
|
|
423
|
+
return { error: `phase dispatch-plan takes one phase. ${usage}` };
|
|
424
|
+
phase = a;
|
|
425
|
+
}
|
|
426
|
+
if (!phase)
|
|
427
|
+
return { error: `phase required for phase dispatch-plan. ${usage}` };
|
|
428
|
+
return { phase, waveFilter, gapsOnly, useWorktrees, tier };
|
|
429
|
+
}
|
|
430
|
+
/** Isolation and concurrency settings: CLI flag, then `.planning/config.json`, then defaults. */
|
|
431
|
+
function resolveDispatchPolicy(cwd, args) {
|
|
432
|
+
let raw = {};
|
|
433
|
+
try {
|
|
434
|
+
raw = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(planningDir(cwd), 'config.json'), 'utf-8'));
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
raw = {};
|
|
438
|
+
}
|
|
439
|
+
const workflow = (raw['workflow'] && typeof raw['workflow'] === 'object' ? raw['workflow'] : {});
|
|
440
|
+
let useWorktrees = true;
|
|
441
|
+
let useWorktreesSource = 'default';
|
|
442
|
+
if (args.useWorktrees !== null) {
|
|
443
|
+
useWorktrees = args.useWorktrees;
|
|
444
|
+
useWorktreesSource = 'cli';
|
|
445
|
+
}
|
|
446
|
+
else if (workflow['use_worktrees'] !== undefined) {
|
|
447
|
+
useWorktrees = workflow['use_worktrees'] !== false && workflow['use_worktrees'] !== 'false';
|
|
448
|
+
useWorktreesSource = 'config';
|
|
449
|
+
}
|
|
450
|
+
let parallelization = true;
|
|
451
|
+
try {
|
|
452
|
+
parallelization = loadConfig(cwd)['parallelization'] !== false;
|
|
453
|
+
}
|
|
454
|
+
catch {
|
|
455
|
+
parallelization = true;
|
|
456
|
+
}
|
|
457
|
+
let submodulePaths = [];
|
|
458
|
+
try {
|
|
459
|
+
submodulePaths = parseGitmodulesPaths(node_fs_1.default.readFileSync(node_path_1.default.join(cwd, '.gitmodules'), 'utf-8'));
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
submodulePaths = [];
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
useWorktrees,
|
|
466
|
+
useWorktreesSource,
|
|
467
|
+
parallelization,
|
|
468
|
+
submodulePaths,
|
|
469
|
+
waveFilter: args.waveFilter,
|
|
470
|
+
gapsOnly: args.gapsOnly,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
@@ -62,6 +62,11 @@ function extractFrontmatter(content) {
|
|
|
62
62
|
const yaml = match[1];
|
|
63
63
|
const lines = yaml.split(/\r?\n/);
|
|
64
64
|
const stack = [{ obj: frontmatter, key: null, indent: -1 }];
|
|
65
|
+
// Indent of the last key that took a scalar value. Lines indented deeper than it
|
|
66
|
+
// are orphaned content of that scalar (a block scalar body, or children YAML would
|
|
67
|
+
// reject). Before this guard they fell through to whatever stack frame was open —
|
|
68
|
+
// often the root — so a nested `status:` overwrote the top-level one (260929-iyi G4).
|
|
69
|
+
let scalarIndent = null;
|
|
65
70
|
for (const line of lines) {
|
|
66
71
|
// Skip empty lines
|
|
67
72
|
if (line.trim() === '')
|
|
@@ -69,6 +74,11 @@ function extractFrontmatter(content) {
|
|
|
69
74
|
// Calculate indentation (number of leading spaces)
|
|
70
75
|
const indentMatch = line.match(/^(\s*)/);
|
|
71
76
|
const indent = indentMatch ? indentMatch[1].length : 0;
|
|
77
|
+
if (scalarIndent !== null) {
|
|
78
|
+
if (indent > scalarIndent)
|
|
79
|
+
continue;
|
|
80
|
+
scalarIndent = null;
|
|
81
|
+
}
|
|
72
82
|
// Pop stack back to appropriate level
|
|
73
83
|
while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
|
|
74
84
|
stack.pop();
|
|
@@ -78,7 +88,10 @@ function extractFrontmatter(content) {
|
|
|
78
88
|
const keyMatch = line.match(/^(\s*)([a-zA-Z0-9_-]+):\s*(.*)/);
|
|
79
89
|
if (keyMatch) {
|
|
80
90
|
const key = keyMatch[2];
|
|
81
|
-
|
|
91
|
+
// A value that is only a YAML comment (`key: # note`) is an empty value, so
|
|
92
|
+
// the key opens a nested block like a bare `key:` does (260929-iyi G4).
|
|
93
|
+
const rawValue = keyMatch[3].trim();
|
|
94
|
+
const value = rawValue.startsWith('#') ? '' : rawValue;
|
|
82
95
|
if (value === '' || value === '[') {
|
|
83
96
|
// Key with no value or opening bracket — could be nested object or array
|
|
84
97
|
const newObj = value === '[' ? [] : {};
|
|
@@ -91,11 +104,13 @@ function extractFrontmatter(content) {
|
|
|
91
104
|
// Inline array: key: [a, b, c] — quote-aware split (REG-04 fix)
|
|
92
105
|
current.obj[key] = splitInlineArray(value.slice(1, -1));
|
|
93
106
|
current.key = null;
|
|
107
|
+
scalarIndent = indent;
|
|
94
108
|
}
|
|
95
109
|
else {
|
|
96
110
|
// Simple key: value
|
|
97
111
|
current.obj[key] = value.replace(/^["']|["']$/g, '');
|
|
98
112
|
current.key = null;
|
|
113
|
+
scalarIndent = indent;
|
|
99
114
|
}
|
|
100
115
|
}
|
|
101
116
|
else if (line.trim().startsWith('- ')) {
|
|
@@ -375,7 +390,7 @@ function parseMustHavesBlock(content, blockName) {
|
|
|
375
390
|
}
|
|
376
391
|
// ─── Frontmatter CRUD commands ────────────────────────────────────────────────
|
|
377
392
|
const FRONTMATTER_SCHEMAS = {
|
|
378
|
-
plan: { required: ['phase', 'plan', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'] },
|
|
393
|
+
plan: { required: ['phase', 'plan', 'title', 'type', 'wave', 'depends_on', 'files_modified', 'autonomous', 'must_haves'] },
|
|
379
394
|
summary: { required: ['phase', 'plan', 'subsystem', 'tags', 'duration', 'completed'] },
|
|
380
395
|
verification: { required: ['phase', 'verified', 'status', 'score'] },
|
|
381
396
|
};
|
|
@@ -30,19 +30,22 @@ function routeInitCommand({ init, args, cwd, raw, error }) {
|
|
|
30
30
|
unknownMessage: (_subcommand, available) => `Unknown init workflow: ${_subcommand}\nAvailable: ${available.join(', ')}`,
|
|
31
31
|
handlers: {
|
|
32
32
|
'execute-phase': () => {
|
|
33
|
-
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, [], ['validate', 'tdd']);
|
|
34
|
-
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'] });
|
|
33
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['tier'], ['validate', 'tdd']);
|
|
34
|
+
init.cmdInitExecutePhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], tier: namedArgs['tier'] });
|
|
35
35
|
},
|
|
36
36
|
'plan-phase': () => {
|
|
37
|
-
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity'], ['validate', 'tdd']);
|
|
38
|
-
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], granularity: namedArgs['granularity'] });
|
|
37
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['granularity', 'tier'], ['validate', 'tdd']);
|
|
38
|
+
init.cmdInitPlanPhase(cwd, args[2], raw, { validate: namedArgs['validate'], tdd: namedArgs['tdd'], granularity: namedArgs['granularity'], tier: namedArgs['tier'] });
|
|
39
39
|
},
|
|
40
40
|
'new-project': () => init.cmdInitNewProject(cwd, raw),
|
|
41
41
|
'new-milestone': () => init.cmdInitNewMilestone(cwd, raw),
|
|
42
42
|
quick: () => init.cmdInitQuick(cwd, args.slice(2).join(' '), raw),
|
|
43
43
|
'ingest-docs': () => init.cmdInitIngestDocs(cwd, raw),
|
|
44
44
|
resume: () => init.cmdInitResume(cwd, raw),
|
|
45
|
-
'verify-work': () =>
|
|
45
|
+
'verify-work': () => {
|
|
46
|
+
const namedArgs = (0, command_arg_projection_cjs_1.parseNamedArgs)(args, ['tier'], []);
|
|
47
|
+
init.cmdInitVerifyWork(cwd, args[2], raw, { tier: namedArgs['tier'] });
|
|
48
|
+
},
|
|
46
49
|
'phase-op': () => init.cmdInitPhaseOp(cwd, args[2], raw),
|
|
47
50
|
todos: () => init.cmdInitTodos(cwd, args[2], raw),
|
|
48
51
|
'milestone-op': () => init.cmdInitMilestoneOp(cwd, raw),
|