@pareto4/pp-core 1.13.0 → 1.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +3 -3
- package/README.md +26 -9
- package/agents/pp-code-reviewer.md +2 -1
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +10 -13
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +244 -21
- package/agents/pp-verifier.md +7 -3
- package/bin/install.js +10 -3
- package/commands/pp-execute-phase.md +1 -3
- package/commands/pp-ns-review.md +1 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +5 -3
- package/commands/pp-verify-work.md +1 -2
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-check-update.js +0 -0
- package/hooks/dist/pp-cursor-session-start.js +1 -1
- package/hooks/dist/pp-statusline.js +1 -1
- package/hooks/dist/pp-update-banner.js +0 -0
- package/hooks/pp-check-update.js +0 -0
- package/hooks/pp-cursor-session-start.js +1 -1
- package/hooks/pp-statusline.js +1 -1
- package/hooks/pp-update-banner.js +0 -0
- package/package.json +14 -19
- package/pareto-skills/_shared/create-project-board.sh +0 -0
- package/pareto-skills/pp-project-setup/SKILL.md +1 -1
- package/pp-core/bin/check-latest-version.cjs +0 -0
- package/pp-core/bin/detect-build-commands.sh +2 -2
- package/pp-core/bin/lib/capability-activation.cjs +83 -9
- package/pp-core/bin/lib/capability-registry.cjs +396 -82
- package/pp-core/bin/lib/capability-state.cjs +160 -4
- package/pp-core/bin/lib/capability-writer.cjs +19 -5
- package/pp-core/bin/lib/check-command-router.cjs +3334 -946
- package/pp-core/bin/lib/clusters.cjs +3 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -35
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +251 -88
- package/pp-core/bin/lib/config-loader.cjs +7 -103
- package/pp-core/bin/lib/config-schema.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +154 -62
- package/pp-core/bin/lib/decisions.cjs +4 -4
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -38
- package/pp-core/bin/lib/git-base-branch.cjs +13 -11
- package/pp-core/bin/lib/git-provider.cjs +4 -4
- package/pp-core/bin/lib/init-command-router.cjs +5 -8
- package/pp-core/bin/lib/init-contract.cjs +648 -738
- package/pp-core/bin/lib/init-interactive.cjs +76 -4
- package/pp-core/bin/lib/init.cjs +24 -51
- package/pp-core/bin/lib/install-profiles.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +8 -0
- package/pp-core/bin/lib/loop-resolver.cjs +189 -129
- package/pp-core/bin/lib/org-profile.cjs +469 -152
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -29
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -253
- package/pp-core/bin/lib/platform-bindings.cjs +40 -19
- package/pp-core/bin/lib/practices-review-check.cjs +15 -9
- package/pp-core/bin/lib/profile-output.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +104 -1
- package/pp-core/bin/lib/state.cjs +11 -85
- package/pp-core/bin/lib/template.cjs +1 -1
- package/pp-core/bin/lib/vault-audit.cjs +650 -126
- package/pp-core/bin/lib/vault-command-router.cjs +2604 -367
- package/pp-core/bin/lib/vault-coordinates.cjs +13 -4
- package/pp-core/bin/lib/vault-index.cjs +699 -0
- package/pp-core/bin/lib/vault-mode.cjs +235 -0
- package/pp-core/bin/lib/vault-practices.cjs +139 -72
- package/pp-core/bin/lib/vault-review.cjs +194 -451
- package/pp-core/bin/lib/vault-seal.cjs +1936 -0
- package/pp-core/bin/lib/vault-standards.cjs +1505 -0
- package/pp-core/bin/lib/vault-trace.cjs +607 -254
- package/pp-core/bin/lib/vault-update.cjs +9 -33
- package/pp-core/bin/lib/vault-write-back.cjs +70 -42
- package/pp-core/bin/lib/vault.cjs +2734 -920
- package/pp-core/bin/lib/verify.cjs +0 -11
- package/pp-core/bin/portable-timeout.sh +1 -1
- package/pp-core/bin/pp-tools.cjs +184 -54
- package/pp-core/bin/shared/config-defaults.manifest.json +0 -7
- package/pp-core/bin/shared/config-schema.manifest.json +0 -3
- package/pp-core/bin/shared/model-catalog.json +2 -2
- package/pp-core/bin/verify-reapply-patches.cjs +0 -0
- package/pp-core/org-profile.json +26 -0
- package/pp-core/references/autonomous-best-effort.md +8 -7
- package/pp-core/references/loop-hook-dispatch.md +0 -25
- package/pp-core/references/planner-gap-closure.md +0 -1
- package/pp-core/references/planner-tdd.md +0 -1
- package/pp-core/references/planner-vault-practices.md +17 -34
- package/pp-core/references/planner-vault-standards.md +320 -0
- package/pp-core/references/planning-config.md +0 -8
- package/pp-core/references/practice-arithmetic-contract.md +121 -0
- package/pp-core/references/tdd.md +0 -1
- package/pp-core/references/verifier-practice-verdicts.md +263 -0
- package/pp-core/templates/README.md +1 -0
- package/pp-core/templates/VAULT-REVIEW.md +202 -0
- package/pp-core/templates/phase-prompt.md +2 -6
- package/pp-core/templates/practices/README.md +20 -23
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -4
- package/pp-core/workflows/audit-fix.md +63 -2
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/autonomous.md +85 -11
- package/pp-core/workflows/check-todos.md +13 -2
- package/pp-core/workflows/code-review-fix.md +59 -3
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +54 -4
- package/pp-core/workflows/discovery-sprint/prefill.md +19 -7
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -49
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +2 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +20 -24
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +1 -6
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +1 -3
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +1 -3
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +1 -1
- package/pp-core/workflows/discovery-sprint/vault-context.md +213 -0
- package/pp-core/workflows/discovery-sprint.md +7 -3
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +4 -4
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +43 -1
- package/pp-core/workflows/discovery-to-build.md +10 -2
- package/pp-core/workflows/discuss-phase.md +3 -10
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +329 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +3 -2
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -1
- package/pp-core/workflows/execute-phase/steps/execute-pre-dispatch.md +98 -0
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +94 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +6 -8
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +115 -14
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +322 -20
- package/pp-core/workflows/execute-phase/steps/verifier-practice-payload.md +165 -0
- package/pp-core/workflows/execute-phase/steps/verify-post-gate.md +163 -0
- package/pp-core/workflows/execute-phase.md +155 -128
- package/pp-core/workflows/execute-plan.md +56 -6
- package/pp-core/workflows/fast.md +53 -4
- package/pp-core/workflows/help/modes/full.md +3 -3
- package/pp-core/workflows/init/steps/client-context-capture.md +101 -0
- package/pp-core/workflows/init/steps/gitlab-provisioning.md +130 -0
- package/pp-core/workflows/init/steps/project-md-deferral-narration.md +66 -0
- package/pp-core/workflows/init/steps/reachability-outcome.md +70 -0
- package/pp-core/workflows/init.md +232 -272
- package/pp-core/workflows/new-project.md +31 -23
- package/pp-core/workflows/plan-phase.md +98 -45
- package/pp-core/workflows/quick.md +117 -3
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -28
- package/pp-core/workflows/undo.md +1 -1
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +58 -3
- package/pp-core/workflows/vault-review.md +282 -22
- package/pp-core/workflows/verify-work.md +12 -14
- package/scripts/changeset/cli.cjs +6 -5
- package/scripts/changeset/lint.cjs +0 -0
- package/scripts/changeset/new.cjs +33 -50
- package/scripts/dev-env.cjs +25 -85
- package/scripts/gen-capability-registry.cjs +199 -11
- package/scripts/gen-practice-arithmetic-contract.cjs +412 -0
- package/scripts/lib/allowlist-ratchet.cjs +1 -103
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -2
- package/scripts/lint-docs-required.cjs +0 -0
- package/scripts/lint-pr-check-project-dir.cjs +1 -3
- package/scripts/lint-test-file-count.allowlist.json +12 -10
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -17
- package/scripts/record-vault-mode-golden.cjs +89 -0
- package/scripts/release-channel.cjs +0 -0
- package/scripts/sync-manifest-versions.cjs +5 -4
- package/scripts/sync-rulesets.sh +1 -1
- package/scripts/update-size-baseline.cjs +30 -274
- package/scripts/vault-calibrate.cjs +996 -0
- package/scripts/verify-opengsd-sweep.cjs +3 -5
- package/scripts/workflow-size.cjs +9 -61
- package/pp-core/bin/lib/dispatch-plan.cjs +0 -483
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/loop-gates.cjs +0 -213
- package/pp-core/bin/lib/phase-manifest.cjs +0 -527
- package/pp-core/bin/lib/phase-tier.cjs +0 -480
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/executor-vault-trace.md +0 -47
- package/pp-core/references/planner-plan-graph.md +0 -66
- package/pp-core/workflows/discovery-sprint/vault-fetch.md +0 -37
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +0 -45
- package/pp-core/workflows/execute-phase/steps/install-run.md +0 -29
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +0 -23
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +0 -73
- package/pp-core/workflows/execute-phase/steps/vault-door-gate.md +0 -19
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -43
- package/scripts/lib/size-budget.cjs +0 -311
- package/scripts/lint-neutrality-names.cjs +0 -694
- package/scripts/lint-org-coordinates.allowlist.json +0 -35
- package/scripts/lint-org-coordinates.cjs +0 -426
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Phase tier — the single owner of how much ceremony a phase runs.
|
|
4
|
-
*
|
|
5
|
-
* A tier is a named preset over the existing gate booleans
|
|
6
|
-
* `workflow.{research, pattern_mapper, plan_check, verifier,
|
|
7
|
-
* nyquist_validation, code_review, post_planning_gaps, security_enforcement,
|
|
8
|
-
* vault_review}`:
|
|
9
|
-
*
|
|
10
|
-
* light — planner and executor only; every gate off.
|
|
11
|
-
* standard — verifier and code review (with its fix loop) on, plan checker
|
|
12
|
-
* ONE pass and no bounce; everything else off. Schema default.
|
|
13
|
-
* heavy — every gate on, the plan-checker loop up to its maximum
|
|
14
|
-
* (`PLAN_CHECK_MAX_PASSES`, the plan-phase revision cap), plan
|
|
15
|
-
* bounce on, and the fresh-install verification slot
|
|
16
|
-
* (`install_verification`).
|
|
17
|
-
*
|
|
18
|
-
* `vault_review` is the agent practice review (`pp-vault-reviewer`): on only in
|
|
19
|
-
* heavy, and spawned only where the project's vault practices apply
|
|
20
|
-
* (`vaultReviewApplies`, src/vault.cts). The vault's load, trace and audit gates
|
|
21
|
-
* are NOT tier gates: they are cheap code checks and run in every tier.
|
|
22
|
-
*
|
|
23
|
-
* ## Precedence (`resolvePhaseTier`)
|
|
24
|
-
*
|
|
25
|
-
* CLI `--tier` > the phase's ROADMAP entry `**Tier:**` field > project
|
|
26
|
-
* `phase.tier` in `.planning/config.json` > schema default `standard`.
|
|
27
|
-
*
|
|
28
|
-
* The ROADMAP entry is the ONE owner of the phase field: it exists for every
|
|
29
|
-
* phase before discuss runs, and a light phase may skip discuss (so a
|
|
30
|
-
* CONTEXT.md field would be missing exactly where it matters).
|
|
31
|
-
*
|
|
32
|
-
* A gate boolean the project EXPLICITLY sets to `false` in its own config
|
|
33
|
-
* wins over every tier (listed in `suppressed` when the tier wanted it on). A
|
|
34
|
-
* boolean the project explicitly sets to `true` does NOT force it on: the tier
|
|
35
|
-
* decides on-ness; the project can only switch a gate off. "Explicit" means
|
|
36
|
-
* present in the project's own config.json (workstream, then root) — a value
|
|
37
|
-
* inherited from the manifest default is not explicit.
|
|
38
|
-
*
|
|
39
|
-
* ## Proposal (`proposePhaseTier`)
|
|
40
|
-
*
|
|
41
|
-
* heavy — a plan touches a gate (the vault's gate, verdict and redaction
|
|
42
|
-
* owners included), init, redaction, config-precedence or
|
|
43
|
-
* hook-dispatch file (`HEAVY_PATH_RULES`), or — before any plan
|
|
44
|
-
* exists — the requirement tags or ROADMAP text name one
|
|
45
|
-
* (`HEAVY_TEXT_RULES`);
|
|
46
|
-
* light — plans exist and none touches a source file (`isSourceFile`,
|
|
47
|
-
* the plan-graph predicate);
|
|
48
|
-
* standard — otherwise.
|
|
49
|
-
*
|
|
50
|
-
* Readers: `init plan-phase` / `init execute-phase` / `init verify-work`,
|
|
51
|
-
* `loop render-hooks --phase`, `phase.dispatch-plan`, `phase.tier` and
|
|
52
|
-
* `phase.tier-propose`. Workflows never resolve a tier themselves.
|
|
53
|
-
*/
|
|
54
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
55
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
56
|
-
};
|
|
57
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
-
exports.HEAVY_TEXT_RULES = exports.HEAVY_PATH_RULES = exports.TIER_PRESETS = exports.DEFAULT_PLAN_BOUNCE_PASSES = exports.PLAN_CHECK_MAX_PASSES = exports.TIER_GATE_KEYS = exports.SCHEMA_DEFAULT_TIER = exports.PHASE_TIERS = void 0;
|
|
59
|
-
exports.isPhaseTier = isPhaseTier;
|
|
60
|
-
exports.resolvePhaseTier = resolvePhaseTier;
|
|
61
|
-
exports.heavyCategoryForPath = heavyCategoryForPath;
|
|
62
|
-
exports.proposePhaseTier = proposePhaseTier;
|
|
63
|
-
exports.readProjectTierConfig = readProjectTierConfig;
|
|
64
|
-
exports.parsePhaseTierField = parsePhaseTierField;
|
|
65
|
-
exports.readPhaseRecord = readPhaseRecord;
|
|
66
|
-
exports.resolvePhaseTierForProject = resolvePhaseTierForProject;
|
|
67
|
-
exports.overlayTierGates = overlayTierGates;
|
|
68
|
-
exports.recordPhaseTier = recordPhaseTier;
|
|
69
|
-
exports.parseTierFlag = parseTierFlag;
|
|
70
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
71
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
72
|
-
const plan_graph_cjs_1 = require("./plan-graph.cjs");
|
|
73
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
74
|
-
const planningWorkspace = require("./planning-workspace.cjs");
|
|
75
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
76
|
-
const roadmapParser = require("./roadmap-parser.cjs");
|
|
77
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
78
|
-
const phaseIdModule = require("./phase-id.cjs");
|
|
79
|
-
const { planningDir, planningRoot, withPlanningLock } = planningWorkspace;
|
|
80
|
-
const { getRoadmapPhaseInternal } = roadmapParser;
|
|
81
|
-
const { phaseMarkdownRegexSource } = phaseIdModule;
|
|
82
|
-
exports.PHASE_TIERS = ['light', 'standard', 'heavy'];
|
|
83
|
-
exports.SCHEMA_DEFAULT_TIER = 'standard';
|
|
84
|
-
exports.TIER_GATE_KEYS = [
|
|
85
|
-
'research',
|
|
86
|
-
'pattern_mapper',
|
|
87
|
-
'plan_check',
|
|
88
|
-
'verifier',
|
|
89
|
-
'nyquist_validation',
|
|
90
|
-
'code_review',
|
|
91
|
-
'post_planning_gaps',
|
|
92
|
-
'security_enforcement',
|
|
93
|
-
'vault_review',
|
|
94
|
-
];
|
|
95
|
-
/**
|
|
96
|
-
* The plan-checker loop's current maximum: plan-phase §12 "Revision Loop (Max
|
|
97
|
-
* 3 Iterations)" — the checker runs once after planning and at most twice more
|
|
98
|
-
* after a revision (verify-work's gap-plan loop has the same cap).
|
|
99
|
-
*/
|
|
100
|
-
exports.PLAN_CHECK_MAX_PASSES = 3;
|
|
101
|
-
/** `workflow.plan_bounce_passes` schema default (config-defaults manifest). */
|
|
102
|
-
exports.DEFAULT_PLAN_BOUNCE_PASSES = 2;
|
|
103
|
-
function gates(on) {
|
|
104
|
-
const out = {};
|
|
105
|
-
for (const k of exports.TIER_GATE_KEYS)
|
|
106
|
-
out[k] = on.includes(k);
|
|
107
|
-
return out;
|
|
108
|
-
}
|
|
109
|
-
exports.TIER_PRESETS = Object.freeze({
|
|
110
|
-
light: { gates: gates([]), plan_check_passes: 0, plan_bounce: false, install_verification: false },
|
|
111
|
-
standard: {
|
|
112
|
-
gates: gates(['plan_check', 'verifier', 'code_review']),
|
|
113
|
-
plan_check_passes: 1,
|
|
114
|
-
plan_bounce: false,
|
|
115
|
-
install_verification: false,
|
|
116
|
-
},
|
|
117
|
-
heavy: {
|
|
118
|
-
gates: gates([...exports.TIER_GATE_KEYS]),
|
|
119
|
-
plan_check_passes: exports.PLAN_CHECK_MAX_PASSES,
|
|
120
|
-
plan_bounce: true,
|
|
121
|
-
install_verification: true,
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
function isPhaseTier(value) {
|
|
125
|
-
return typeof value === 'string' && exports.PHASE_TIERS.includes(value);
|
|
126
|
-
}
|
|
127
|
-
function isExplicitFalse(value) {
|
|
128
|
-
return value === false || value === 'false';
|
|
129
|
-
}
|
|
130
|
-
function isExplicitTrue(value) {
|
|
131
|
-
return value === true || value === 'true';
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Resolve the effective gate set for one phase.
|
|
135
|
-
*
|
|
136
|
-
* `cli` must already be a valid tier or null (the CLI rejects anything else).
|
|
137
|
-
*/
|
|
138
|
-
function resolvePhaseTier(input) {
|
|
139
|
-
const ignored = [];
|
|
140
|
-
const candidates = [
|
|
141
|
-
{ source: 'cli', value: input.cli },
|
|
142
|
-
{ source: 'phase', value: input.phaseRecord?.tier },
|
|
143
|
-
{ source: 'project', value: input.config?.tier },
|
|
144
|
-
];
|
|
145
|
-
let tier = exports.SCHEMA_DEFAULT_TIER;
|
|
146
|
-
let source = 'default';
|
|
147
|
-
for (const c of candidates) {
|
|
148
|
-
if (c.value === null || c.value === undefined || c.value === '')
|
|
149
|
-
continue;
|
|
150
|
-
const v = String(c.value).trim().toLowerCase();
|
|
151
|
-
if (isPhaseTier(v)) {
|
|
152
|
-
tier = v;
|
|
153
|
-
source = c.source;
|
|
154
|
-
break;
|
|
155
|
-
}
|
|
156
|
-
ignored.push({ source: c.source, value: String(c.value) });
|
|
157
|
-
}
|
|
158
|
-
const preset = exports.TIER_PRESETS[tier];
|
|
159
|
-
const explicit = input.config?.explicit ?? {};
|
|
160
|
-
const suppressed = [];
|
|
161
|
-
const notForced = [];
|
|
162
|
-
const resolve = (key, wanted) => {
|
|
163
|
-
const value = explicit[key];
|
|
164
|
-
if (wanted && isExplicitFalse(value)) {
|
|
165
|
-
suppressed.push(key);
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
if (!wanted && isExplicitTrue(value))
|
|
169
|
-
notForced.push(key);
|
|
170
|
-
return wanted;
|
|
171
|
-
};
|
|
172
|
-
const out = {};
|
|
173
|
-
for (const key of exports.TIER_GATE_KEYS)
|
|
174
|
-
out[key] = resolve(key, preset.gates[key]);
|
|
175
|
-
out.plan_check_passes = out.plan_check ? preset.plan_check_passes : 0;
|
|
176
|
-
out.plan_bounce = resolve('plan_bounce', preset.plan_bounce);
|
|
177
|
-
const passes = input.config?.plan_bounce_passes;
|
|
178
|
-
out.plan_bounce_passes = out.plan_bounce
|
|
179
|
-
? (typeof passes === 'number' && Number.isInteger(passes) && passes > 0 ? passes : exports.DEFAULT_PLAN_BOUNCE_PASSES)
|
|
180
|
-
: 0;
|
|
181
|
-
out.install_verification = preset.install_verification;
|
|
182
|
-
return { tier, source, gates: out, suppressed, not_forced: notForced, ignored };
|
|
183
|
-
}
|
|
184
|
-
/** `src/<name>.cts` or its compiled `pp-core/bin/lib/<name>.cjs`. */
|
|
185
|
-
const LIB = String.raw `^(?:src|pp-core/bin/lib)/`;
|
|
186
|
-
/**
|
|
187
|
-
* Paths that make a phase heavy. Matched against a plan's `files_modified`
|
|
188
|
-
* (normalised, repo-relative). A matching file must also be a source file by
|
|
189
|
-
* the plan-graph predicate, so a test or doc named after a gate never counts.
|
|
190
|
-
*/
|
|
191
|
-
exports.HEAVY_PATH_RULES = Object.freeze([
|
|
192
|
-
{ category: 'gate', example: 'src/ui-safety-gate.cts', pattern: new RegExp(`${LIB}(?:[^/]+/)*[^/]*gate[^/]*\\.c?[jt]s$`) },
|
|
193
|
-
{ category: 'gate', example: 'src/check-command-router.cts', pattern: new RegExp(`${LIB}(?:check-command-router|prohibition-enforcement|gap-checker|plan-graph|dispatch-plan)\\.c?[jt]s$`) },
|
|
194
|
-
{ category: 'gate', example: 'src/practices-review-check.cts', pattern: new RegExp(`${LIB}[^/]+-(?:check|guard)\\.c?[jt]s$`) },
|
|
195
|
-
// The vault's gate, verdict and redaction owners (`vault`, `vault-trace`,
|
|
196
|
-
// `vault-audit`, `vault-review`, `vault-practices`, …): a phase editing them
|
|
197
|
-
// changes what the load, trace and audit gates decide and what remote text is
|
|
198
|
-
// redacted, so it is proposed heavy.
|
|
199
|
-
{ category: 'gate', example: 'src/vault.cts', pattern: new RegExp(`${LIB}vault[^/]*\\.c?[jt]s$`) },
|
|
200
|
-
{ category: 'init', example: 'src/init.cts', pattern: new RegExp(`${LIB}init[^/]*\\.c?[jt]s$`) },
|
|
201
|
-
{ category: 'redaction', example: 'src/observability/redaction.cts', pattern: /(?:^|\/)[^/]*(?:redact|secret)[^/]*$/i },
|
|
202
|
-
{ category: 'config-precedence', example: 'src/config-loader.cts', pattern: new RegExp(`${LIB}(?:config[^/]*|configuration|federated-config|capability-activation|phase-tier)\\.c?[jt]s$`) },
|
|
203
|
-
{ category: 'config-precedence', example: 'pp-core/bin/shared/config-defaults.manifest.json', pattern: /^pp-core\/bin\/shared\/config-[^/]+\.json$/ },
|
|
204
|
-
{ category: 'hook-dispatch', example: 'hooks/pp-workflow-guard.js', pattern: /^hooks\// },
|
|
205
|
-
{ category: 'hook-dispatch', example: 'src/loop-resolver.cts', pattern: new RegExp(`${LIB}(?:loop-resolver|capability-state|runtime-hooks-surface)\\.c?[jt]s$`) },
|
|
206
|
-
{ category: 'hook-dispatch', example: 'capabilities/code-review/capability.json', pattern: /^capabilities\/[^/]+\/capability\.json$/ },
|
|
207
|
-
{ category: 'hook-dispatch', example: 'scripts/build-hooks.js', pattern: /^scripts\/build-hooks\.js$/ },
|
|
208
|
-
]);
|
|
209
|
-
/** Words in requirement tags or ROADMAP text that make a not-yet-planned phase heavy. */
|
|
210
|
-
exports.HEAVY_TEXT_RULES = Object.freeze([
|
|
211
|
-
{ category: 'gate', pattern: /\b(?:gates?|gating)\b/i },
|
|
212
|
-
{ category: 'init', pattern: /\b(?:init|pp-init|initiali[sz]ation)\b/i },
|
|
213
|
-
{ category: 'redaction', pattern: /\b(?:redact(?:s|ed|ion)?|secrets?)\b/i },
|
|
214
|
-
{ category: 'config-precedence', pattern: /\bconfig(?:uration)?\s+(?:precedence|resolution|loader|defaults)\b|\bprecedence\b/i },
|
|
215
|
-
{ category: 'hook-dispatch', pattern: /\bhooks?\b[^.\n]{0,40}\bdispatch\w*\b|\bloop[- ]hooks?\b|\bcapability registry\b/i },
|
|
216
|
-
]);
|
|
217
|
-
/** Requirement-tag prefixes that name a heavy category (e.g. `INIT-02`). */
|
|
218
|
-
const HEAVY_TAG_PREFIX = Object.freeze([
|
|
219
|
-
{ category: 'gate', pattern: /^GATES?-/i },
|
|
220
|
-
{ category: 'init', pattern: /^INIT-/i },
|
|
221
|
-
{ category: 'redaction', pattern: /^(?:REDACT|REDACTION|SECRETS?)-/i },
|
|
222
|
-
{ category: 'config-precedence', pattern: /^(?:CONFIG|CFG|PREC)-/i },
|
|
223
|
-
{ category: 'hook-dispatch', pattern: /^HOOKS?-/i },
|
|
224
|
-
]);
|
|
225
|
-
function normalizePath(file) {
|
|
226
|
-
return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
227
|
-
}
|
|
228
|
-
function heavyCategoryForPath(file) {
|
|
229
|
-
const f = normalizePath(file);
|
|
230
|
-
if (f === '' || !(0, plan_graph_cjs_1.isSourceFile)(f))
|
|
231
|
-
return null;
|
|
232
|
-
for (const rule of exports.HEAVY_PATH_RULES)
|
|
233
|
-
if (rule.pattern.test(f))
|
|
234
|
-
return rule;
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
function proposePhaseTier(input) {
|
|
238
|
-
const plans = input.plans ?? [];
|
|
239
|
-
if (plans.length > 0) {
|
|
240
|
-
const heavy = [];
|
|
241
|
-
const source = [];
|
|
242
|
-
for (const p of plans) {
|
|
243
|
-
const files = (p.files_modified ?? []).map(normalizePath).filter((f) => f !== '');
|
|
244
|
-
if (files.length === 0)
|
|
245
|
-
source.push({ category: 'undeclared', plan: p.id });
|
|
246
|
-
for (const f of files) {
|
|
247
|
-
const rule = heavyCategoryForPath(f);
|
|
248
|
-
if (rule)
|
|
249
|
-
heavy.push({ category: rule.category, plan: p.id, file: f });
|
|
250
|
-
else if ((0, plan_graph_cjs_1.isSourceFile)(f))
|
|
251
|
-
source.push({ category: 'source', plan: p.id, file: f });
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (heavy.length > 0) {
|
|
255
|
-
const cats = [...new Set(heavy.map((m) => m.category))].join(', ');
|
|
256
|
-
return {
|
|
257
|
-
tier: 'heavy',
|
|
258
|
-
basis: 'plans',
|
|
259
|
-
reason: `a plan touches ${cats} code (${heavy.slice(0, 3).map((m) => `${m.plan}: ${m.file}`).join('; ')}${heavy.length > 3 ? '; …' : ''})`,
|
|
260
|
-
matched: heavy,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
if (source.length === 0) {
|
|
264
|
-
return { tier: 'light', basis: 'plans', reason: 'no plan touches a source file; only docs, tests, workflow and planning files', matched: [] };
|
|
265
|
-
}
|
|
266
|
-
const undeclared = source.filter((m) => m.category === 'undeclared').map((m) => m.plan);
|
|
267
|
-
return {
|
|
268
|
-
tier: 'standard',
|
|
269
|
-
basis: 'plans',
|
|
270
|
-
reason: undeclared.length > 0 && undeclared.length === source.length
|
|
271
|
-
? `plan(s) ${undeclared.join(', ')} declare no files_modified, so source is assumed`
|
|
272
|
-
: 'plans touch source files, none of them gate, init, redaction, config-precedence or hook-dispatch code',
|
|
273
|
-
matched: source,
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
const matched = [];
|
|
277
|
-
for (const tag of input.requirements ?? []) {
|
|
278
|
-
const t = String(tag).trim();
|
|
279
|
-
for (const r of HEAVY_TAG_PREFIX)
|
|
280
|
-
if (r.pattern.test(t))
|
|
281
|
-
matched.push({ category: r.category, text: t });
|
|
282
|
-
}
|
|
283
|
-
// The phase's own **Tier:** line (and a recorded proposal's reason) is never evidence.
|
|
284
|
-
const text = (input.roadmapText ?? '').replace(/^\*\*Tier(?::\*\*|\*\*:)[^\n]*$/gim, '');
|
|
285
|
-
for (const r of exports.HEAVY_TEXT_RULES) {
|
|
286
|
-
const m = r.pattern.exec(text);
|
|
287
|
-
if (m)
|
|
288
|
-
matched.push({ category: r.category, text: m[0] });
|
|
289
|
-
}
|
|
290
|
-
if (matched.length > 0) {
|
|
291
|
-
const cats = [...new Set(matched.map((m) => m.category))].join(', ');
|
|
292
|
-
return { tier: 'heavy', basis: 'text', reason: `no plans yet; the requirements or ROADMAP entry name ${cats} work (${matched.slice(0, 3).map((m) => `"${m.text}"`).join(', ')})`, matched };
|
|
293
|
-
}
|
|
294
|
-
return { tier: 'standard', basis: 'text', reason: 'no plans yet and nothing names gate, init, redaction, config-precedence or hook-dispatch work; planning re-proposes from files_modified', matched: [] };
|
|
295
|
-
}
|
|
296
|
-
// ─── I/O: project config, phase record, recording ─────────────────────────────
|
|
297
|
-
const FLAT_ALIASES = {
|
|
298
|
-
research: 'research',
|
|
299
|
-
plan_check: 'plan_checker',
|
|
300
|
-
verifier: 'verifier',
|
|
301
|
-
nyquist_validation: 'nyquist_validation',
|
|
302
|
-
post_planning_gaps: 'post_planning_gaps',
|
|
303
|
-
};
|
|
304
|
-
function readJson(file) {
|
|
305
|
-
try {
|
|
306
|
-
const parsed = JSON.parse(node_fs_1.default.readFileSync(file, 'utf-8'));
|
|
307
|
-
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
308
|
-
}
|
|
309
|
-
catch {
|
|
310
|
-
return null;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
function lookup(cfg, section, key, flat) {
|
|
314
|
-
if (flat !== undefined && cfg[flat] !== undefined)
|
|
315
|
-
return cfg[flat];
|
|
316
|
-
const sec = cfg[section];
|
|
317
|
-
if (sec && typeof sec === 'object' && !Array.isArray(sec)) {
|
|
318
|
-
const v = sec[key];
|
|
319
|
-
if (v !== undefined)
|
|
320
|
-
return v;
|
|
321
|
-
}
|
|
322
|
-
return undefined;
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* The project's own config (workstream config.json first, then the root
|
|
326
|
-
* config.json): `phase.tier`, the gate booleans actually written there, and
|
|
327
|
-
* `workflow.plan_bounce_passes`. Manifest defaults are never read here.
|
|
328
|
-
*/
|
|
329
|
-
function readProjectTierConfig(cwd) {
|
|
330
|
-
const files = [node_path_1.default.join(planningDir(cwd), 'config.json')];
|
|
331
|
-
const root = node_path_1.default.join(planningRoot(cwd), 'config.json');
|
|
332
|
-
if (root !== files[0])
|
|
333
|
-
files.push(root);
|
|
334
|
-
const configs = files.map(readJson).filter((c) => c !== null);
|
|
335
|
-
const first = (section, key, flat) => {
|
|
336
|
-
for (const c of configs) {
|
|
337
|
-
const v = lookup(c, section, key, flat);
|
|
338
|
-
if (v !== undefined)
|
|
339
|
-
return v;
|
|
340
|
-
}
|
|
341
|
-
return undefined;
|
|
342
|
-
};
|
|
343
|
-
const explicit = {};
|
|
344
|
-
for (const key of [...exports.TIER_GATE_KEYS, 'plan_bounce']) {
|
|
345
|
-
const v = first('workflow', key, FLAT_ALIASES[key]);
|
|
346
|
-
if (v !== undefined)
|
|
347
|
-
explicit[key] = v;
|
|
348
|
-
}
|
|
349
|
-
const tier = first('phase', 'tier');
|
|
350
|
-
const passes = first('workflow', 'plan_bounce_passes');
|
|
351
|
-
const script = first('workflow', 'plan_bounce_script');
|
|
352
|
-
return {
|
|
353
|
-
tier: typeof tier === 'string' ? tier : null,
|
|
354
|
-
explicit,
|
|
355
|
-
plan_bounce_passes: typeof passes === 'number' ? passes : null,
|
|
356
|
-
plan_bounce_script: typeof script === 'string' && script.trim() !== '' ? script : null,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
const TIER_FIELD_RE = /^\*\*Tier(?::\*\*|\*\*:)[^\S\n]*([A-Za-z-]+)([^\n]*)$/im;
|
|
360
|
-
/** The `**Tier:**` field of a ROADMAP phase section, or null when absent. */
|
|
361
|
-
function parsePhaseTierField(section) {
|
|
362
|
-
if (!section)
|
|
363
|
-
return null;
|
|
364
|
-
const m = TIER_FIELD_RE.exec(section);
|
|
365
|
-
if (!m)
|
|
366
|
-
return null;
|
|
367
|
-
return { tier: m[1].toLowerCase(), proposed: /\(\s*proposed\b/i.test(m[2]), line: m[0] };
|
|
368
|
-
}
|
|
369
|
-
function readPhaseRecord(cwd, phase) {
|
|
370
|
-
const rp = getRoadmapPhaseInternal(cwd, phase);
|
|
371
|
-
const section = rp?.section ?? null;
|
|
372
|
-
const field = parsePhaseTierField(section);
|
|
373
|
-
return {
|
|
374
|
-
tier: field?.tier ?? null,
|
|
375
|
-
proposed: field?.proposed ?? false,
|
|
376
|
-
section,
|
|
377
|
-
phase_name: rp?.phase_name ?? null,
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
/** Resolve the tier of `phase` in the project at `cwd` (CLI value already validated). */
|
|
381
|
-
function resolvePhaseTierForProject(cwd, phase, cli) {
|
|
382
|
-
const config = readProjectTierConfig(cwd);
|
|
383
|
-
const record = phase ? readPhaseRecord(cwd, phase) : null;
|
|
384
|
-
const phaseRecord = record && record.tier !== null ? { tier: record.tier, proposed: record.proposed } : null;
|
|
385
|
-
const resolved = resolvePhaseTier({ cli, phaseRecord, config });
|
|
386
|
-
return { ...resolved, phase_record: phaseRecord, project_tier: config.tier ?? null, plan_bounce_script: config.plan_bounce_script };
|
|
387
|
-
}
|
|
388
|
-
/**
|
|
389
|
-
* The gate booleans of a resolved tier as a config overlay
|
|
390
|
-
* (`{ workflow: { research: …, … } }`), merged over `base` without mutating
|
|
391
|
-
* it. Used by `loop render-hooks --phase` so capability `when` keys and
|
|
392
|
-
* activation keys read the tier-resolved values first.
|
|
393
|
-
*/
|
|
394
|
-
function overlayTierGates(base, resolved) {
|
|
395
|
-
const wf = base['workflow'] && typeof base['workflow'] === 'object' && !Array.isArray(base['workflow'])
|
|
396
|
-
? { ...base['workflow'] }
|
|
397
|
-
: {};
|
|
398
|
-
for (const key of exports.TIER_GATE_KEYS)
|
|
399
|
-
wf[key] = resolved.gates[key];
|
|
400
|
-
wf['plan_bounce'] = resolved.gates.plan_bounce;
|
|
401
|
-
const out = { ...base, workflow: wf };
|
|
402
|
-
for (const [key, flat] of Object.entries(FLAT_ALIASES)) {
|
|
403
|
-
if (flat !== undefined && flat in base)
|
|
404
|
-
out[flat] = resolved.gates[key];
|
|
405
|
-
}
|
|
406
|
-
return out;
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Write `**Tier:** <tier>` into the phase's ROADMAP entry (current milestone
|
|
410
|
-
* only). A proposal (`proposed: true`) is written as `**Tier:** <tier>
|
|
411
|
-
* (proposed: <reason>)` and never replaces an operator choice (a field without
|
|
412
|
-
* the `(proposed` marker). An operator choice always replaces the field.
|
|
413
|
-
*/
|
|
414
|
-
function recordPhaseTier(cwd, phase, tier, opts) {
|
|
415
|
-
const roadmapPath = node_path_1.default.join(planningDir(cwd), 'ROADMAP.md');
|
|
416
|
-
if (!node_fs_1.default.existsSync(roadmapPath))
|
|
417
|
-
return { recorded: false, tier: null, proposed: false, reason: 'ROADMAP.md not found' };
|
|
418
|
-
let result = { recorded: false, tier: null, proposed: false, reason: 'phase entry not found in ROADMAP.md' };
|
|
419
|
-
withPlanningLock(cwd, () => {
|
|
420
|
-
const content = node_fs_1.default.readFileSync(roadmapPath, 'utf-8');
|
|
421
|
-
const lastClose = content.lastIndexOf('</details>');
|
|
422
|
-
const offset = lastClose === -1 ? 0 : lastClose + '</details>'.length;
|
|
423
|
-
const scope = content.slice(offset);
|
|
424
|
-
const header = new RegExp(`^#{2,4}\\s*(?:\\[[^\\]]+\\]\\s*)?Phase\\s+${phaseMarkdownRegexSource(phase)}\\s*:[^\\n]*$`, 'im');
|
|
425
|
-
const hm = header.exec(scope);
|
|
426
|
-
if (!hm)
|
|
427
|
-
return;
|
|
428
|
-
const start = hm.index;
|
|
429
|
-
const after = scope.slice(start + hm[0].length);
|
|
430
|
-
const next = /\n#{2,4}\s+(?:\[[^\]]+\]\s*)?Phase\s+[\w]|\n#{1,2}\s+(?!#)/.exec(after);
|
|
431
|
-
const end = start + hm[0].length + (next ? next.index : after.length);
|
|
432
|
-
const section = scope.slice(start, end);
|
|
433
|
-
const existing = parsePhaseTierField(section);
|
|
434
|
-
if (existing && opts.proposed && !existing.proposed) {
|
|
435
|
-
result = { recorded: false, tier: existing.tier, proposed: false, reason: `kept the operator's choice (${existing.tier})` };
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
const reason = (opts.reason ?? '').replace(/\s+/g, ' ').trim();
|
|
439
|
-
const line = opts.proposed ? `**Tier:** ${tier} (proposed${reason ? `: ${reason}` : ''})` : `**Tier:** ${tier}`;
|
|
440
|
-
let newSection;
|
|
441
|
-
if (existing) {
|
|
442
|
-
newSection = section.replace(existing.line, line);
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
const goal = /^\*\*Goal(?:\*\*:|\*?\*?:\*\*)[^\n]*$/im.exec(section);
|
|
446
|
-
const anchorEnd = goal ? goal.index + goal[0].length : hm[0].length;
|
|
447
|
-
newSection = `${section.slice(0, anchorEnd)}\n${line}${section.slice(anchorEnd)}`;
|
|
448
|
-
}
|
|
449
|
-
if (newSection === section) {
|
|
450
|
-
result = { recorded: false, tier, proposed: opts.proposed, reason: 'already recorded' };
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
const updated = content.slice(0, offset) + scope.slice(0, start) + newSection + scope.slice(end);
|
|
454
|
-
node_fs_1.default.writeFileSync(roadmapPath, updated, 'utf-8');
|
|
455
|
-
result = { recorded: true, tier, proposed: opts.proposed, reason: opts.proposed ? 'proposal recorded' : 'operator choice recorded' };
|
|
456
|
-
});
|
|
457
|
-
return result;
|
|
458
|
-
}
|
|
459
|
-
/** Parse a `--tier` value; returns the tier, null when absent, or an error message. */
|
|
460
|
-
function parseTierFlag(args) {
|
|
461
|
-
const rest = [];
|
|
462
|
-
let tier = null;
|
|
463
|
-
for (let i = 0; i < args.length; i++) {
|
|
464
|
-
const a = args[i];
|
|
465
|
-
let value;
|
|
466
|
-
if (a === '--tier')
|
|
467
|
-
value = args[++i];
|
|
468
|
-
else if (a.startsWith('--tier='))
|
|
469
|
-
value = a.slice('--tier='.length);
|
|
470
|
-
else {
|
|
471
|
-
rest.push(a);
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
const v = (value ?? '').trim().toLowerCase();
|
|
475
|
-
if (!isPhaseTier(v))
|
|
476
|
-
return { error: `--tier needs one of ${exports.PHASE_TIERS.join(', ')}` };
|
|
477
|
-
tier = v;
|
|
478
|
-
}
|
|
479
|
-
return { tier, rest };
|
|
480
|
-
}
|