@pareto4/pp-core 1.11.0 → 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 +128 -15
- 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 +213 -33
- package/pp-core/bin/lib/init-interactive.cjs +20 -1
- 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 +158 -8
- 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
|
@@ -31,6 +31,8 @@ const schema_detect_cjs_1 = require("./schema-detect.cjs");
|
|
|
31
31
|
const artifacts_cjs_1 = require("./artifacts.cjs");
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- agent-install-check.cjs is an export= CommonJS module
|
|
33
33
|
const agentInstallCheck = require("./agent-install-check.cjs");
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- milestone.cjs is an export= CommonJS module
|
|
35
|
+
const milestoneMod = require("./milestone.cjs");
|
|
34
36
|
const { checkAgentsInstalled } = agentInstallCheck;
|
|
35
37
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
38
|
const ioMod = require("./io.cjs");
|
|
@@ -42,15 +44,16 @@ const { loadConfig, CONFIG_DEFAULTS } = configLoaderMod;
|
|
|
42
44
|
const phaseIdMod = require("./phase-id.cjs");
|
|
43
45
|
const { normalizePhaseName, phaseTokenMatches, escapeRegex, getMilestoneFromPhaseId } = phaseIdMod;
|
|
44
46
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
45
|
-
const
|
|
46
|
-
const {
|
|
47
|
+
const phaseScopeMod = require("./phase-scope.cjs");
|
|
48
|
+
const { resolvePhaseForRead } = phaseScopeMod;
|
|
47
49
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
50
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
49
|
-
const {
|
|
51
|
+
const { getDeclaredMilestoneInfo, stripShippedMilestones, extractCurrentMilestone } = roadmapParserMod;
|
|
50
52
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
51
53
|
const worktreeSafetyMod = require("./worktree-safety.cjs");
|
|
52
54
|
const { inspectWorktreeHealth } = worktreeSafetyMod;
|
|
53
|
-
const
|
|
55
|
+
const plan_graph_cjs_1 = require("./plan-graph.cjs");
|
|
56
|
+
const { planningDir, milestonePathsFor, resolveProjectFile } = planningWorkspace;
|
|
54
57
|
const { extractFrontmatter, parseMustHavesBlock } = frontmatterMod;
|
|
55
58
|
const { writeStateMd } = stateMod;
|
|
56
59
|
const { MODEL_PROFILES } = modelProfilesMod;
|
|
@@ -538,6 +541,16 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
538
541
|
if (fm[field] === undefined)
|
|
539
542
|
errors.push(`Missing required frontmatter field: ${field}`);
|
|
540
543
|
}
|
|
544
|
+
// `title:` is required on every plan not yet executed. A plan that already has a
|
|
545
|
+
// SUMMARY predates the rule (or ran without it): its missing title only warns.
|
|
546
|
+
const titleProblem = (0, plan_graph_cjs_1.planTitleProblem)(fm['title']);
|
|
547
|
+
if (titleProblem) {
|
|
548
|
+
const summaryPath = fullPath.replace(/PLAN\.md$/, 'SUMMARY.md');
|
|
549
|
+
if (summaryPath !== fullPath && node_fs_1.default.existsSync(summaryPath))
|
|
550
|
+
warnings.push(`${titleProblem} (executed plan: warning only)`);
|
|
551
|
+
else
|
|
552
|
+
errors.push(titleProblem);
|
|
553
|
+
}
|
|
541
554
|
const taskPattern = /<task[^>]*>([\s\S]*?)<\/task>/g;
|
|
542
555
|
const tasks = [];
|
|
543
556
|
let taskMatch;
|
|
@@ -592,7 +605,7 @@ function cmdVerifyPhaseCompleteness(cwd, phase, raw) {
|
|
|
592
605
|
if (!phase) {
|
|
593
606
|
error('phase required');
|
|
594
607
|
}
|
|
595
|
-
const phaseInfoRaw =
|
|
608
|
+
const phaseInfoRaw = resolvePhaseForRead(cwd, phase);
|
|
596
609
|
if (!phaseInfoRaw || !phaseInfoRaw['found']) {
|
|
597
610
|
output({ error: 'Phase not found', phase }, raw);
|
|
598
611
|
return;
|
|
@@ -898,7 +911,10 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
898
911
|
}, raw, allVerified ? 'valid' : 'invalid');
|
|
899
912
|
}
|
|
900
913
|
function listMilestoneArchiveDirs(planBase) {
|
|
901
|
-
|
|
914
|
+
// CARRY-05: one owner for the archive-directory location, shared with the
|
|
915
|
+
// `milestone complete` writer. planBase is planningDir(cwd) at every call
|
|
916
|
+
// site, so this already followed --ws; routing it here keeps it that way.
|
|
917
|
+
const milestonesDir = milestonePathsFor(planBase).milestonesArchive;
|
|
902
918
|
try {
|
|
903
919
|
return node_fs_1.default
|
|
904
920
|
.readdirSync(milestonesDir, { withFileTypes: true })
|
|
@@ -938,7 +954,7 @@ function getActiveMilestoneArchiveDir(planBase) {
|
|
|
938
954
|
const m = state.match(/^\s*(?:\*\*)?milestone(?:\*\*)?:\s*\*{0,2}\s*([^\s*\r\n#][^\s\r\n#]*)/mi);
|
|
939
955
|
if (m && m[1]) {
|
|
940
956
|
const milestone = m[1].trim();
|
|
941
|
-
const candidate = node_path_1.default.join(planBase,
|
|
957
|
+
const candidate = node_path_1.default.join(milestonePathsFor(planBase).milestonesArchive, `${milestone}-phases`);
|
|
942
958
|
return archiveDirs.includes(candidate) ? candidate : null;
|
|
943
959
|
}
|
|
944
960
|
}
|
|
@@ -1113,7 +1129,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1113
1129
|
return;
|
|
1114
1130
|
}
|
|
1115
1131
|
const planBase = planningDir(cwd);
|
|
1116
|
-
|
|
1132
|
+
// D1 (261007-jkl): E002/W001 judge the PROJECT.md that answers for this cwd —
|
|
1133
|
+
// the workstream's own, else the shared one — not a workstream path nothing creates.
|
|
1134
|
+
const projectPath = resolveProjectFile(cwd).path;
|
|
1117
1135
|
const roadmapPath = node_path_1.default.join(planBase, 'ROADMAP.md');
|
|
1118
1136
|
const statePath = node_path_1.default.join(planBase, 'STATE.md');
|
|
1119
1137
|
const configPath = node_path_1.default.join(planBase, 'config.json');
|
|
@@ -1134,7 +1152,7 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1134
1152
|
info.push(issue);
|
|
1135
1153
|
};
|
|
1136
1154
|
if (!node_fs_1.default.existsSync(planBase)) {
|
|
1137
|
-
addIssue('error', 'E001', '.planning/ directory not found', `Run ${slash('
|
|
1155
|
+
addIssue('error', 'E001', '.planning/ directory not found', `Run ${slash('init')} to initialize`);
|
|
1138
1156
|
output({ status: 'broken', errors, warnings, info, repairable_count: 0 }, raw);
|
|
1139
1157
|
return;
|
|
1140
1158
|
}
|
|
@@ -1440,8 +1458,8 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1440
1458
|
catch {
|
|
1441
1459
|
/* W021 check is advisory — skip on error */
|
|
1442
1460
|
}
|
|
1443
|
-
|
|
1444
|
-
const milestonesArchiveDir =
|
|
1461
|
+
// CARRY-05: W018's two paths come from the owner, not a local join.
|
|
1462
|
+
const { milestones: milestonesPath, milestonesArchive: milestonesArchiveDir } = milestonePathsFor(planBase);
|
|
1445
1463
|
const missingFromRegistry = [];
|
|
1446
1464
|
try {
|
|
1447
1465
|
if (node_fs_1.default.existsSync(milestonesArchiveDir)) {
|
|
@@ -1530,8 +1548,13 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1530
1548
|
}
|
|
1531
1549
|
// ─── Perform repairs if requested ─────────────────────────────────────────
|
|
1532
1550
|
const repairActions = [];
|
|
1533
|
-
|
|
1551
|
+
// 261007-rly D-9: `--backfill` alone runs the backfill W018 names as its
|
|
1552
|
+
// remedy (health.md documents it as "triggered by `--backfill`"); every other
|
|
1553
|
+
// repair still needs `--repair`.
|
|
1554
|
+
if ((options['repair'] || options['backfill']) && repairs.length > 0) {
|
|
1534
1555
|
for (const repair of repairs) {
|
|
1556
|
+
if (!options['repair'] && repair !== 'backfillMilestones')
|
|
1557
|
+
continue;
|
|
1535
1558
|
try {
|
|
1536
1559
|
switch (repair) {
|
|
1537
1560
|
case 'createConfig':
|
|
@@ -1564,22 +1587,24 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1564
1587
|
node_fs_1.default.copyFileSync(statePath, backupPath);
|
|
1565
1588
|
repairActions.push({ action: 'backupState', success: true, path: backupPath });
|
|
1566
1589
|
}
|
|
1567
|
-
const milestone =
|
|
1590
|
+
const milestone = getDeclaredMilestoneInfo(cwd); // 261007-ixt W4: never a guessed milestone
|
|
1591
|
+
// D1 (261007-jkl): reference the PROJECT.md that answers for this cwd.
|
|
1568
1592
|
const projectRef = node_path_1.default
|
|
1569
|
-
.relative(cwd,
|
|
1593
|
+
.relative(cwd, resolveProjectFile(cwd).path)
|
|
1570
1594
|
.split(node_path_1.default.sep)
|
|
1571
1595
|
.join('/');
|
|
1572
1596
|
let stateContent = `# Session State\n\n`;
|
|
1573
1597
|
stateContent += `## Project Reference\n\n`;
|
|
1574
1598
|
stateContent += `See: ${projectRef}\n\n`;
|
|
1575
1599
|
stateContent += `## Position\n\n`;
|
|
1576
|
-
stateContent += `**Milestone:** ${milestone.version} ${milestone.name}\n`;
|
|
1600
|
+
stateContent += `**Milestone:** ${milestone ? `${milestone.version} ${milestone.name}` : 'none declared in ROADMAP.md'}\n`;
|
|
1577
1601
|
stateContent += `**Current phase:** (determining...)\n`;
|
|
1578
1602
|
stateContent += `**Status:** Resuming\n\n`;
|
|
1579
1603
|
stateContent += `## Session Log\n\n`;
|
|
1580
1604
|
stateContent += `- ${new Date().toISOString().split('T')[0]}: STATE.md regenerated by ${slash('health')} --repair\n`;
|
|
1581
|
-
|
|
1582
|
-
|
|
1605
|
+
// 261007-ixt D-3: the repair names every frontmatter key it wrote.
|
|
1606
|
+
const stateReport = writeStateMd(statePath, stateContent, cwd);
|
|
1607
|
+
repairActions.push({ action: repair, success: true, path: 'STATE.md', frontmatter_updated: stateReport.frontmatter_updated });
|
|
1583
1608
|
break;
|
|
1584
1609
|
}
|
|
1585
1610
|
case 'addNyquistKey': {
|
|
@@ -1647,20 +1672,17 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1647
1672
|
const milestonesContent = node_fs_1.default.existsSync(milestonesPath)
|
|
1648
1673
|
? node_fs_1.default.readFileSync(milestonesPath, 'utf-8')
|
|
1649
1674
|
: '';
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, entry + milestonesContent);
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1675
|
+
// 261007-rly D-6: the same record writer `milestone complete` uses —
|
|
1676
|
+
// one index row (the version and the snapshot read are all a backfill
|
|
1677
|
+
// knows; every other cell is the unknown marker) and the section after
|
|
1678
|
+
// the table, never between the H1 and the table.
|
|
1679
|
+
const snapshotLink = node_path_1.default.relative(node_path_1.default.dirname(milestonesPath), snapshotPath).split(node_path_1.default.sep).join('/');
|
|
1680
|
+
const record = milestoneMod.insertMilestoneRecord(milestonesContent, {
|
|
1681
|
+
version: ver,
|
|
1682
|
+
entry,
|
|
1683
|
+
cells: { version: ver, archive: `[${ver}-ROADMAP.md](${snapshotLink})` },
|
|
1684
|
+
});
|
|
1685
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(milestonesPath, record.content);
|
|
1664
1686
|
backfilled++;
|
|
1665
1687
|
}
|
|
1666
1688
|
catch {
|
|
@@ -102,7 +102,7 @@ function cmdWorkstreamCreate(cwd, name, options, raw) {
|
|
|
102
102
|
}
|
|
103
103
|
const baseDir = planningRoot(cwd);
|
|
104
104
|
if (!node_fs_1.default.existsSync(baseDir)) {
|
|
105
|
-
error(`.planning/ directory not found — run ${(0, runtime_slash_cjs_1.
|
|
105
|
+
error(`.planning/ directory not found — run ${(0, runtime_slash_cjs_1.formatPpSlash)('init', (0, runtime_slash_cjs_1.resolveRuntime)(cwd))} first`);
|
|
106
106
|
}
|
|
107
107
|
const wsRoot = node_path_1.default.join(baseDir, 'workstreams');
|
|
108
108
|
const wsDir = node_path_1.default.join(wsRoot, slug);
|
|
@@ -409,9 +409,10 @@ function defaultFindSummaryFiles(worktreePath) {
|
|
|
409
409
|
* For each *SUMMARY.md found under <worktreePath>/.planning/:
|
|
410
410
|
* - compute relative path from worktree root → .planning/<id>-SUMMARY.md
|
|
411
411
|
* - if the file is ALREADY COMMITTED on the worktree branch
|
|
412
|
-
* (`git
|
|
413
|
-
* the merge will carry it naturally and copying it as
|
|
414
|
-
* cause a "untracked working tree files would be
|
|
412
|
+
* (`git ls-tree -z --full-tree --name-only HEAD -- <relPath>` prints the path),
|
|
413
|
+
* skip the copy entirely: the merge will carry it naturally and copying it as
|
|
414
|
+
* an untracked file would cause a "untracked working tree files would be
|
|
415
|
+
* overwritten by merge" collision. Empty ls-tree output means not committed.
|
|
415
416
|
* On timeout or fatal exit (128) the rescue is also skipped (fail-closed).
|
|
416
417
|
* (#706 — execute-phase committed-SUMMARY contract)
|
|
417
418
|
* - destination = <repoRoot>/<relPath>
|
|
@@ -442,27 +443,37 @@ function rescueSummaryArtifacts(worktreePath, repoRoot, deps) {
|
|
|
442
443
|
// output works on Windows too (git always emits forward slashes in porcelain output).
|
|
443
444
|
const relPath = absPath.slice(worktreePath.length).replace(/^[/\\]/, '').replace(/\\/g, '/');
|
|
444
445
|
// #706: skip rescue when the SUMMARY is already committed on the branch.
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
// the
|
|
450
|
-
//
|
|
446
|
+
// Ask the committed HEAD tree directly with `git ls-tree -z --name-only
|
|
447
|
+
// HEAD -- <relPath>` (not `ls-files --error-unmatch`, which also matches
|
|
448
|
+
// staged-but-uncommitted files the merge would NOT carry). ls-tree exits 0
|
|
449
|
+
// in both answers: the path on stdout when it is in HEAD, empty stdout when
|
|
450
|
+
// it is not. That is the property the previous form lacked: `cat-file -e
|
|
451
|
+
// HEAD:<relPath>` resolves the object NAME first and dies (exit 128, "path
|
|
452
|
+
// '…' does not exist in 'HEAD'") for any path not in HEAD — on every git
|
|
453
|
+
// from 1.7 through 2.55 — while its exit 1 is reserved for a raw OID missing
|
|
454
|
+
// from the store. So the fail-closed arm below swallowed every uncommitted
|
|
455
|
+
// SUMMARY and cleanup-wave blocked with worktree_dirty on each /pp-quick
|
|
456
|
+
// worktree run (261002 vault-gate probe). `-z` keeps the comparison byte-
|
|
457
|
+
// exact: without it core.quotePath would C-quote a non-ASCII path and a
|
|
458
|
+
// committed file would read as absent, re-creating the #706 collision.
|
|
451
459
|
//
|
|
452
460
|
// Fail-closed on timeout/fatal git errors: if we cannot determine whether
|
|
453
461
|
// the file is committed, do NOT rescue it (rescuing an actually-committed
|
|
454
462
|
// file would re-create the untracked collision; the merge will surface the
|
|
455
463
|
// issue). The cleanup will be blocked by merge_failed in the worst case,
|
|
456
464
|
// which is the observable behaviour before this fix and is recoverable.
|
|
457
|
-
const
|
|
458
|
-
if (
|
|
459
|
-
// Rescue only when cat-file definitively reports the object is absent (exit 1).
|
|
460
|
-
// exit 0 → object exists (committed on HEAD) — merge will carry it, skip.
|
|
465
|
+
const treeCheck = execGit(['-C', worktreePath, 'ls-tree', '-z', '--full-tree', '--name-only', 'HEAD', '--', relPath], { cwd: repoRoot });
|
|
466
|
+
if (!gitResultOk(treeCheck)) {
|
|
461
467
|
// exit 128 → fatal git error (corrupt store, unborn HEAD, etc.) — uncertain,
|
|
462
468
|
// fail-closed: do NOT rescue to avoid recreating the #706 collision.
|
|
463
469
|
// timedOut / null / other → unreliable result — same fail-closed policy.
|
|
464
|
-
//
|
|
465
|
-
//
|
|
470
|
+
// The merge then surfaces the problem safely, which is the recoverable
|
|
471
|
+
// pre-fix behaviour.
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
const committedOnHead = (treeCheck.stdout || '').split('\0').some((line) => line === relPath);
|
|
475
|
+
if (committedOnHead) {
|
|
476
|
+
// Object exists in the committed HEAD tree — the merge will carry it, skip.
|
|
466
477
|
continue;
|
|
467
478
|
}
|
|
468
479
|
const dest = node_path_1.default.join(repoRoot, relPath);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# portable-timeout.sh — THE portable timeout wrapper for this repository (D-09-03).
|
|
3
|
+
#
|
|
4
|
+
# SOURCED, never executed. It defines three functions and nothing else, and it
|
|
5
|
+
# deliberately sets no `set -e` / `set -u`: it is sourced into shells that may
|
|
6
|
+
# or may not use them.
|
|
7
|
+
#
|
|
8
|
+
# Consumers:
|
|
9
|
+
# - scripts/base64-scan.sh (CI, executed)
|
|
10
|
+
# - the gate blocks in pp-core/workflows/ (runtime; wired by plan 09-02)
|
|
11
|
+
#
|
|
12
|
+
# Why it lives in pp-core/bin/ rather than scripts/:
|
|
13
|
+
# `scripts/` shell files are not installed — pp-file-manifest.json carries no
|
|
14
|
+
# `scripts/*.sh` entry, so an installed workflow can neither find nor source
|
|
15
|
+
# scripts/base64-scan.sh (which also ends in an unguarded `main "$@"`).
|
|
16
|
+
# `pp-core/` is copied wholesale at install time — bin/install.js's
|
|
17
|
+
# copyWithPathReplacement() falls through to fs.copyFileSync for any non-.md
|
|
18
|
+
# file, so a .sh arrives byte-identical — it is in package.json "files", and
|
|
19
|
+
# it already hosts the tracked shell launcher `pp_run`. `pp-core/bin/lib/` was
|
|
20
|
+
# deliberately NOT used: several .cjs files there are gitignored build output
|
|
21
|
+
# and the directory reads as compiler output.
|
|
22
|
+
#
|
|
23
|
+
# MEASURED exit-code matrix (macOS 26.5, bash 3.2.57, no timeout and no
|
|
24
|
+
# gtimeout on PATH, perl at /usr/bin/perl; re-measured 2026-09-19 and pinned by
|
|
25
|
+
# tests/gate-shell-owners.test.cjs):
|
|
26
|
+
#
|
|
27
|
+
# run_with_timeout 1 sleep 5 -> 142 (perl) / 124 (GNU)
|
|
28
|
+
# run_with_timeout 5 bash -c '<missing command>' -> 127 under every backend
|
|
29
|
+
# run_with_timeout 5 <missing binary> -> 2 under perl, 127 GNU
|
|
30
|
+
# run_with_timeout 5 true -> 0
|
|
31
|
+
# run_with_timeout 5 bash -c 'exit 7' -> 7
|
|
32
|
+
#
|
|
33
|
+
# CAVEAT, and the reason that third row is pinned by a test: under the perl
|
|
34
|
+
# backend a DIRECT exec of a missing binary exits 2 — perl's own
|
|
35
|
+
# `die "exec: $!"` — not 127. A caller that wants a portable "never ran"
|
|
36
|
+
# signal must wrap its command in `bash -c "$CMD"`. Every gate block does,
|
|
37
|
+
# except the two that exec a binary directly (code-review.md's Fallow binary
|
|
38
|
+
# and ship.md's ${REVIEW_CMD}); those see 2, not 127, when the binary is absent.
|
|
39
|
+
#
|
|
40
|
+
# Backend selection is injectable for tests by assigning _TIMEOUT_CMD AFTER
|
|
41
|
+
# sourcing (`. portable-timeout.sh; _TIMEOUT_CMD=perl_alarm`) — _init_timeout_cmd
|
|
42
|
+
# returns early when it is already set, so no edit to the moved block was needed
|
|
43
|
+
# to make the backend selectable.
|
|
44
|
+
#
|
|
45
|
+
# Everything below this line is moved BYTE-VERBATIM from
|
|
46
|
+
# scripts/base64-scan.sh:42-83, which now sources this file instead.
|
|
47
|
+
|
|
48
|
+
# -- Portable timeout wrapper (#116) ------------------------------------------
|
|
49
|
+
# macOS does not ship GNU coreutils timeout. We probe for it (or gtimeout
|
|
50
|
+
# from homebrew coreutils), falling back to perl alarm(N)+exec.
|
|
51
|
+
# Exit codes: GNU timeout uses 124 on timeout; perl SIGALRM produces 142.
|
|
52
|
+
# Both are treated as timeout exits by is_timeout_exit() below.
|
|
53
|
+
# Usage: run_with_timeout <seconds> <command> [args...]
|
|
54
|
+
_TIMEOUT_CMD=""
|
|
55
|
+
# shellcheck disable=SC2329 # intentionally defined for use by callers; not called in main loop
|
|
56
|
+
_init_timeout_cmd() {
|
|
57
|
+
if [[ -n "$_TIMEOUT_CMD" ]]; then return; fi
|
|
58
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
59
|
+
_TIMEOUT_CMD="timeout"
|
|
60
|
+
elif command -v gtimeout >/dev/null 2>&1; then
|
|
61
|
+
_TIMEOUT_CMD="gtimeout"
|
|
62
|
+
else
|
|
63
|
+
_TIMEOUT_CMD="perl_alarm"
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# shellcheck disable=SC2329 # intentionally defined for use by callers; not called in main loop
|
|
68
|
+
run_with_timeout() {
|
|
69
|
+
local secs="$1"; shift
|
|
70
|
+
_init_timeout_cmd
|
|
71
|
+
case "$_TIMEOUT_CMD" in
|
|
72
|
+
timeout|gtimeout)
|
|
73
|
+
"$_TIMEOUT_CMD" "$secs" "$@"
|
|
74
|
+
;;
|
|
75
|
+
perl_alarm)
|
|
76
|
+
# perl sets SIGALRM after N seconds, then exec()s the command.
|
|
77
|
+
# Exit 142 (SIGALRM) when timed out.
|
|
78
|
+
perl -e '
|
|
79
|
+
my $secs = shift @ARGV;
|
|
80
|
+
alarm($secs);
|
|
81
|
+
exec(@ARGV) or die "exec: $!\n";
|
|
82
|
+
' -- "$secs" "$@"
|
|
83
|
+
;;
|
|
84
|
+
esac
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# is_timeout_exit: returns 0 (true) if rc indicates a timeout kill.
|
|
88
|
+
# shellcheck disable=SC2329 # intentionally defined for use by callers; not called in main loop
|
|
89
|
+
is_timeout_exit() { [[ "$1" -eq 124 || "$1" -eq 142 ]]; }
|
package/pp-core/bin/pp-tools.cjs
CHANGED
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
* milestone complete <version> Archive milestone, create MILESTONES.md
|
|
58
58
|
* [--name <name>]
|
|
59
59
|
* [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
|
|
60
|
+
* [--stopped-at <text>] New Session "Stopped at:" (else stopped_at is kept)
|
|
60
61
|
*
|
|
61
62
|
* User Story Validation:
|
|
62
63
|
* user-story validate --story "..." Validate "As a / I want to / so that" format
|
|
@@ -525,7 +526,7 @@ async function main() {
|
|
|
525
526
|
const TOP_LEVEL_USAGE = 'Usage: pp-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
526
527
|
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
|
|
527
528
|
'compose-bindings, config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
|
|
528
|
-
'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
529
|
+
'current-timestamp, detect-custom-files, discovery, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
529
530
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
530
531
|
'generate-client-context, generate-dev-preferences, generate-slug, graphify, history-digest, init, init-contract, intel, ' +
|
|
531
532
|
'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, platform-bindings, profile-questionnaire, ' +
|
|
@@ -713,8 +714,8 @@ function extractField(obj, fieldPath) {
|
|
|
713
714
|
* (Phase 34-09's own design hazard) so the interactive flow stays testable
|
|
714
715
|
* with a scripted fake and no pty.
|
|
715
716
|
*/
|
|
716
|
-
function createReadlinePrompter() {
|
|
717
|
-
const rl = readline.createInterface({ input
|
|
717
|
+
function createReadlinePrompter({ input = process.stdin, output = process.stdout } = {}) {
|
|
718
|
+
const rl = readline.createInterface({ input, output });
|
|
718
719
|
return {
|
|
719
720
|
ask(question) {
|
|
720
721
|
let rendered = question.prompt;
|
|
@@ -725,16 +726,29 @@ function createReadlinePrompter() {
|
|
|
725
726
|
)
|
|
726
727
|
.join(' / ');
|
|
727
728
|
rendered += `\n Options: ${optionsRendered}`;
|
|
729
|
+
} else if (question.preselected) {
|
|
730
|
+
// INIT-PATCH-C: an option-less question carrying a `preselected`
|
|
731
|
+
// (e.g. a detected org profile) rendered no default at all today —
|
|
732
|
+
// render one, naming it via `preselectedLabel` when present.
|
|
733
|
+
rendered += `\n Default: ${question.preselectedLabel || question.preselected} (press Enter to accept)`;
|
|
728
734
|
}
|
|
729
735
|
if (question.optionalAnswer) {
|
|
730
|
-
|
|
736
|
+
// The unconditional "Enter skips" hint would be a lie once a
|
|
737
|
+
// default is carried — Enter now ACCEPTS the default above, so the
|
|
738
|
+
// skip hint must point at "none" instead. The question text may
|
|
739
|
+
// already carry that hint (both org profile prompts do), so it is
|
|
740
|
+
// added only for an optional question whose text lacks it (I-1).
|
|
741
|
+
const skipHint = question.preselected ? 'type "none" to skip' : 'press Enter to skip';
|
|
742
|
+
if (!String(question.prompt).toLowerCase().includes(skipHint.toLowerCase())) {
|
|
743
|
+
rendered += `\n (${skipHint})`;
|
|
744
|
+
}
|
|
731
745
|
}
|
|
732
746
|
return new Promise((resolve) => {
|
|
733
747
|
rl.question(`${rendered}\n> `, (answer) => resolve(answer));
|
|
734
748
|
});
|
|
735
749
|
},
|
|
736
750
|
print(text) {
|
|
737
|
-
|
|
751
|
+
output.write(`${text}\n`);
|
|
738
752
|
},
|
|
739
753
|
close() {
|
|
740
754
|
rl.close();
|
|
@@ -811,6 +825,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
811
825
|
case 'init-contract': {
|
|
812
826
|
// init-contract [--questions] [--verify] [--session-next]
|
|
813
827
|
// [--session-answer <stepId>=<value>] [--session-reset] [--offline]
|
|
828
|
+
// [--arm-vault]
|
|
814
829
|
// --questions (also the no-flag default, D-05): read-only, always
|
|
815
830
|
// exits 0, prints the canonical /pp-init question set as data.
|
|
816
831
|
// --verify: runs the end-state invariant check (Phase 34-07 gap
|
|
@@ -844,7 +859,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
844
859
|
);
|
|
845
860
|
if (unrecognizedIcFlag !== undefined) {
|
|
846
861
|
error(
|
|
847
|
-
`Unrecognized flag "${unrecognizedIcFlag}" for init-contract. Supported: --questions, --verify, --session-next, --session-answer, --session-reset, --offline.`,
|
|
862
|
+
`Unrecognized flag "${unrecognizedIcFlag}" for init-contract. Supported: --questions, --verify, --session-next, --session-answer, --session-reset, --offline, --arm-vault.`,
|
|
848
863
|
ERROR_REASON.SDK_FAIL_FAST,
|
|
849
864
|
);
|
|
850
865
|
break;
|
|
@@ -862,6 +877,9 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
862
877
|
icOptions.sessionReset = true;
|
|
863
878
|
} else if (a === '--offline') {
|
|
864
879
|
icOptions.offline = true;
|
|
880
|
+
} else if (a === '--arm-vault') {
|
|
881
|
+
// Pack 6 D-09: set vault.enabled true only where absent.
|
|
882
|
+
icOptions.armVault = true;
|
|
865
883
|
}
|
|
866
884
|
}
|
|
867
885
|
if (sessionAnswerRaw !== undefined) {
|
|
@@ -1402,9 +1420,10 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1402
1420
|
const subcommand = args[1];
|
|
1403
1421
|
if (subcommand === 'complete') {
|
|
1404
1422
|
const milestoneName = parseMultiwordArg(args, 'name');
|
|
1423
|
+
const stoppedAt = parseMultiwordArg(args, 'stopped-at');
|
|
1405
1424
|
const archivePhases = args.includes('--archive-phases');
|
|
1406
1425
|
const force = args.includes('--force');
|
|
1407
|
-
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force }, raw);
|
|
1426
|
+
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force, stoppedAt }, raw);
|
|
1408
1427
|
} else {
|
|
1409
1428
|
error('Unknown milestone subcommand. Available: complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1410
1429
|
}
|
|
@@ -1497,6 +1516,8 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1497
1516
|
if (!process.stdin.isTTY) {
|
|
1498
1517
|
error(
|
|
1499
1518
|
'"pp-tools init" requires an interactive terminal (stdin is not a TTY) and will not prompt. ' +
|
|
1519
|
+
'This command cannot run inside an AI-agent shell or a piped session — open a regular ' +
|
|
1520
|
+
'terminal window (e.g. Terminal, iTerm, or Windows Terminal) and run it there. ' +
|
|
1500
1521
|
'For a non-interactive/CI-driven session, use "pp-tools query init-contract --session-next" / ' +
|
|
1501
1522
|
'"--session-answer <stepId>=<value>" directly instead.',
|
|
1502
1523
|
ERROR_REASON.INIT_NON_INTERACTIVE,
|
|
@@ -1563,9 +1584,24 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1563
1584
|
}
|
|
1564
1585
|
loopActiveCap = value;
|
|
1565
1586
|
}
|
|
1587
|
+
// --phase <N> / --tier <light|standard|heavy>: resolve the phase's
|
|
1588
|
+
// ceremony tier and read the gate booleans from it (pack 9).
|
|
1589
|
+
const loopValue = (flag) => {
|
|
1590
|
+
const eq = args.find(arg => arg.startsWith(`--${flag}=`));
|
|
1591
|
+
if (eq) return eq.slice(flag.length + 3).trim();
|
|
1592
|
+
const idx = args.indexOf(`--${flag}`);
|
|
1593
|
+
if (idx === -1) return undefined;
|
|
1594
|
+
const value = args[idx + 1];
|
|
1595
|
+
if (!value || value.startsWith('--')) {
|
|
1596
|
+
error(`Missing value for --${flag}`, ERROR_REASON ? ERROR_REASON.USAGE : undefined);
|
|
1597
|
+
}
|
|
1598
|
+
return value;
|
|
1599
|
+
};
|
|
1566
1600
|
loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {
|
|
1567
1601
|
configDir: loopConfigDir ? path.resolve(loopConfigDir) : undefined,
|
|
1568
1602
|
activeCap: loopActiveCap,
|
|
1603
|
+
phase: loopValue('phase'),
|
|
1604
|
+
tier: loopValue('tier'),
|
|
1569
1605
|
});
|
|
1570
1606
|
} else {
|
|
1571
1607
|
error(
|
|
@@ -2299,6 +2335,120 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
2299
2335
|
break;
|
|
2300
2336
|
}
|
|
2301
2337
|
|
|
2338
|
+
// ─── Discovery ─────────────────────────────────────────────────────────
|
|
2339
|
+
//
|
|
2340
|
+
// discovery rollup <path> [--check]
|
|
2341
|
+
//
|
|
2342
|
+
// Read-only rollup of a discovery _requirements.md artifact (discovery-v2
|
|
2343
|
+
// Phase 1, REQM-04). Plain mode always exits 0 with JSON + rendered totals
|
|
2344
|
+
// table; --check exits non-zero on drift, marker problems, or parse
|
|
2345
|
+
// failure (D-07/D-08). The dotted form `query discovery.rollup` reaches
|
|
2346
|
+
// here via the dispatcher dot-split above.
|
|
2347
|
+
//
|
|
2348
|
+
// discovery input-check
|
|
2349
|
+
//
|
|
2350
|
+
// Input-package enforcement (discovery-v2 Phase 2, INPUT-02/INPUT-03).
|
|
2351
|
+
// No positional path — fixed convention paths resolve against cwd. Exits
|
|
2352
|
+
// non-zero (discovery_input_check_failed) when a session is Completed
|
|
2353
|
+
// with [TO CONFIRM] markers remaining, a completeness_score counts
|
|
2354
|
+
// unconfirmed topics (D-18/D-19), or git tracks a file under
|
|
2355
|
+
// .planning/discovery-input/confidential/ (D-22). Non-git projects,
|
|
2356
|
+
// missing input packages, and marker-free engagements exit 0. The dotted
|
|
2357
|
+
// form `query discovery.input-check` reaches here the same way.
|
|
2358
|
+
//
|
|
2359
|
+
// discovery research-check
|
|
2360
|
+
//
|
|
2361
|
+
// Research-track enforcement (discovery-v2 Phase 3, RTRK-01/02/03 +
|
|
2362
|
+
// TRACE-05). No positional path — fixed convention paths resolve against
|
|
2363
|
+
// cwd. Exits non-zero (discovery_research_check_failed) on any STRUCTURAL
|
|
2364
|
+
// fault in .planning/discovery/_research.md, _risks.md or _sparring.md, or
|
|
2365
|
+
// in a ⚠️ CONDITIONAL marker that cites them (D-42). Never-blocks contract
|
|
2366
|
+
// (D-43): unfinished research is NOT a fault — an Open Gates-MVP item exits
|
|
2367
|
+
// 0 and is reported under open_by_priority, so a still-running research
|
|
2368
|
+
// item can never block Session 13 sign-off. A project with no research
|
|
2369
|
+
// artifacts, a register absent while a sibling is present, and an absent
|
|
2370
|
+
// _requirements.md all exit 0. The dotted form
|
|
2371
|
+
// `query discovery.research-check` reaches here the same way.
|
|
2372
|
+
//
|
|
2373
|
+
// discovery screen-check
|
|
2374
|
+
//
|
|
2375
|
+
// Screen-inventory and feedback-loop enforcement (discovery-v2 Phase 4,
|
|
2376
|
+
// TRACE-02/TRACE-03/TRACE-04 + GUARD-01). No positional path — fixed
|
|
2377
|
+
// convention paths resolve against cwd. Exits non-zero
|
|
2378
|
+
// (discovery_screen_check_failed) on any STRUCTURAL fault in
|
|
2379
|
+
// .planning/discovery/_screens.md or _feedback.md, in the D-51 computed
|
|
2380
|
+
// coverage block, or in the D-54 build-tree scan that asserts discovery
|
|
2381
|
+
// instrumentation never shipped. Never-blocks contract (D-43): an
|
|
2382
|
+
// unprocessed feedback row is NOT a fault — a round whose every Processing
|
|
2383
|
+
// cell is Open exits 0 and is reported under open_feedback, and the
|
|
2384
|
+
// outstanding-work payload is emitted on the failure path too. Graceful
|
|
2385
|
+
// arms: a project with no discovery package, an absent _screens.md, an
|
|
2386
|
+
// absent _requirements.md, and an absent _risks.md/_research.md all exit 0,
|
|
2387
|
+
// and the build-tree scan does not run at all unless a discovery artifact
|
|
2388
|
+
// was found. The dotted form `query discovery.screen-check` reaches here
|
|
2389
|
+
// the same way.
|
|
2390
|
+
//
|
|
2391
|
+
// discovery element-sweep [--check]
|
|
2392
|
+
//
|
|
2393
|
+
// Lists every functional element of the prototype under
|
|
2394
|
+
// .planning/discovery/prototype/ so each is decided once in _sweep.md;
|
|
2395
|
+
// --check fails (discovery_element_sweep_failed) while one is undecided.
|
|
2396
|
+
|
|
2397
|
+
case 'discovery': {
|
|
2398
|
+
const subcommand = args[1];
|
|
2399
|
+
if (subcommand === 'rollup') {
|
|
2400
|
+
const rollup = require('./lib/discovery-rollup.cjs');
|
|
2401
|
+
try {
|
|
2402
|
+
rollup.cmdDiscoveryRollup(cwd, args, raw);
|
|
2403
|
+
} catch (rollupErr) {
|
|
2404
|
+
error(`discovery rollup: ${rollupErr && rollupErr.message ? rollupErr.message : String(rollupErr)}`, ERROR_REASON.UNKNOWN);
|
|
2405
|
+
}
|
|
2406
|
+
} else if (subcommand === 'input-check') {
|
|
2407
|
+
const inputCheck = require('./lib/discovery-input-check.cjs');
|
|
2408
|
+
try {
|
|
2409
|
+
inputCheck.cmdDiscoveryInputCheck(cwd, args, raw);
|
|
2410
|
+
} catch (inputCheckErr) {
|
|
2411
|
+
error(`discovery input-check: ${inputCheckErr && inputCheckErr.message ? inputCheckErr.message : String(inputCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2412
|
+
}
|
|
2413
|
+
} else if (subcommand === 'research-check') {
|
|
2414
|
+
const researchTrack = require('./lib/discovery-research-track.cjs');
|
|
2415
|
+
try {
|
|
2416
|
+
researchTrack.cmdDiscoveryResearchCheck(cwd, args, raw);
|
|
2417
|
+
} catch (researchCheckErr) {
|
|
2418
|
+
error(`discovery research-check: ${researchCheckErr && researchCheckErr.message ? researchCheckErr.message : String(researchCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2419
|
+
}
|
|
2420
|
+
} else if (subcommand === 'screen-check') {
|
|
2421
|
+
const screens = require('./lib/discovery-screens.cjs');
|
|
2422
|
+
try {
|
|
2423
|
+
screens.cmdDiscoveryScreenCheck(cwd, args, raw);
|
|
2424
|
+
} catch (screenCheckErr) {
|
|
2425
|
+
error(`discovery screen-check: ${screenCheckErr && screenCheckErr.message ? screenCheckErr.message : String(screenCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2426
|
+
}
|
|
2427
|
+
} else if (subcommand === 'element-sweep') {
|
|
2428
|
+
const sweep = require('./lib/discovery-sweep.cjs');
|
|
2429
|
+
try {
|
|
2430
|
+
sweep.cmdDiscoveryElementSweep(cwd, args, raw);
|
|
2431
|
+
} catch (sweepErr) {
|
|
2432
|
+
error(`discovery element-sweep: ${sweepErr && sweepErr.message ? sweepErr.message : String(sweepErr)}`, ERROR_REASON.UNKNOWN);
|
|
2433
|
+
}
|
|
2434
|
+
} else if (subcommand === 'rebaseline-check') {
|
|
2435
|
+
const rebaseline = require('./lib/discovery-rebaseline.cjs');
|
|
2436
|
+
try {
|
|
2437
|
+
rebaseline.cmdDiscoveryRebaselineCheck(cwd, args, raw);
|
|
2438
|
+
} catch (rebaselineCheckErr) {
|
|
2439
|
+
error(`discovery rebaseline-check: ${rebaselineCheckErr && rebaselineCheckErr.message ? rebaselineCheckErr.message : String(rebaselineCheckErr)}`, ERROR_REASON.UNKNOWN);
|
|
2440
|
+
}
|
|
2441
|
+
} else {
|
|
2442
|
+
// WIRING, NOT HELP TEXT: tests/discovery-artifacts.test.cjs derives each
|
|
2443
|
+
// subcommand spelling by regexing `<namespace>_<subcommand>_failed` out of
|
|
2444
|
+
// the engine's exported exit identity, splits this string on commas and
|
|
2445
|
+
// asserts membership. A spelling missing here is a command the engine
|
|
2446
|
+
// claims to be and the router does not dispatch.
|
|
2447
|
+
error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2448
|
+
}
|
|
2449
|
+
break;
|
|
2450
|
+
}
|
|
2451
|
+
|
|
2302
2452
|
case 'effort': {
|
|
2303
2453
|
const subcommand = args[1];
|
|
2304
2454
|
if (subcommand === 'sync') {
|
|
@@ -2519,4 +2669,4 @@ if (require.main === module) {
|
|
|
2519
2669
|
// ─── Exports (for tests) ──────────────────────────────────────────────────────
|
|
2520
2670
|
// ADR-959: export dispatchCapabilityCommand so tests can exercise it with
|
|
2521
2671
|
// synthetic registry + requireModule injections.
|
|
2522
|
-
module.exports = { dispatchCapabilityCommand };
|
|
2672
|
+
module.exports = { dispatchCapabilityCommand, createReadlinePrompter };
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
"workflow.plan_bounce",
|
|
32
32
|
"workflow.plan_bounce_script",
|
|
33
33
|
"workflow.plan_bounce_passes",
|
|
34
|
+
"size_budget.per_file_bytes",
|
|
35
|
+
"size_budget.per_commit_bytes",
|
|
36
|
+
"phase.tier",
|
|
34
37
|
"workflow.plan_chunked",
|
|
35
38
|
"workflow.plan_review_convergence",
|
|
36
39
|
"code_quality.fallow.enabled",
|