@pareto4/pp-core 1.12.1 → 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 +3 -2
- package/agents/pp-debug-session-manager.md +9 -6
- package/agents/pp-executor.md +7 -21
- package/agents/pp-plan-checker.md +4 -4
- package/agents/pp-planner.md +23 -9
- package/agents/pp-vault-reviewer.md +274 -0
- 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 +2 -1
- package/commands/pp-plan-phase.md +1 -2
- package/commands/pp-vault-review.md +35 -0
- 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 +526 -22
- 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 +3867 -274
- package/pp-core/bin/lib/clusters.cjs +4 -0
- package/pp-core/bin/lib/command-aliases.cjs +0 -28
- package/pp-core/bin/lib/commands.cjs +9 -4
- package/pp-core/bin/lib/compose-bindings.cjs +253 -76
- 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 +104 -27
- package/pp-core/bin/lib/discovery-vault-check.cjs +246 -0
- package/pp-core/bin/lib/frontmatter.cjs +1 -1
- package/pp-core/bin/lib/git-base-branch.cjs +138 -10
- 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 +653 -629
- package/pp-core/bin/lib/init-interactive.cjs +77 -21
- 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 +169 -47
- package/pp-core/bin/lib/org-profile.cjs +544 -53
- package/pp-core/bin/lib/package-identity.cjs +3 -3
- package/pp-core/bin/lib/phase-command-router.cjs +1 -23
- package/pp-core/bin/lib/phase-scope.cjs +1 -1
- package/pp-core/bin/lib/phase.cjs +11 -203
- 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 +2594 -0
- package/pp-core/bin/lib/vault-command-router.cjs +2756 -60
- 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 +1194 -89
- package/pp-core/bin/lib/vault-review.cjs +2102 -0
- 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 +3184 -0
- package/pp-core/bin/lib/vault-update.cjs +242 -0
- package/pp-core/bin/lib/vault-write-back.cjs +78 -31
- package/pp-core/bin/lib/vault.cjs +2713 -634
- 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 -37
- 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 +1 -0
- 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 -14
- 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 +31 -3
- 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 +137 -11
- package/pp-core/templates/project-charter.md +226 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +0 -0
- package/pp-core/workflows/add-tests.md +55 -0
- package/pp-core/workflows/audit-fix.md +63 -0
- 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 -1
- package/pp-core/workflows/code-review.md +10 -1
- package/pp-core/workflows/complete-milestone.md +1 -1
- package/pp-core/workflows/debug.md +56 -2
- package/pp-core/workflows/discovery-sprint/prefill.md +21 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +46 -48
- 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 +9 -1
- 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 -18
- 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 +154 -110
- package/pp-core/workflows/execute-plan.md +58 -4
- package/pp-core/workflows/fast.md +53 -0
- package/pp-core/workflows/help/modes/full.md +1 -0
- 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 +99 -43
- package/pp-core/workflows/quick.md +120 -2
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/ship.md +57 -24
- package/pp-core/workflows/update.md +3 -3
- package/pp-core/workflows/validate-phase.md +60 -1
- package/pp-core/workflows/vault-review.md +325 -0
- package/pp-core/workflows/verify-work.md +13 -12
- 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 -0
- 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 +13 -2
- package/scripts/practice-arithmetic-contract.allow.json +35 -0
- package/scripts/prompt-injection-scan.sh +22 -15
- 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 -472
- package/pp-core/bin/lib/install-run.cjs +0 -536
- package/pp-core/bin/lib/phase-tier.cjs +0 -467
- package/pp-core/bin/lib/plan-graph.cjs +0 -368
- package/pp-core/references/planner-plan-graph.md +0 -66
- 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 -72
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +0 -34
- 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,368 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Plan graph — the single owner of how a phase's plans are ordered.
|
|
4
|
-
*
|
|
5
|
-
* Computes, from each PLAN.md's frontmatter (`depends_on`, `files_modified`,
|
|
6
|
-
* `serial_group`, `chain_reason`, task shapes):
|
|
7
|
-
*
|
|
8
|
-
* - the wave of every plan;
|
|
9
|
-
* - the serial groups (plans that share a wave but must run one after
|
|
10
|
-
* another because they touch the same non-source file);
|
|
11
|
-
* - the chain metrics (plan count, wave count, longest chain, and how many
|
|
12
|
-
* links of that chain exist only because two plans touch the same
|
|
13
|
-
* non-source file);
|
|
14
|
-
* - the chain check the plan checker enforces (a longest chain longer than
|
|
15
|
-
* half the plan count FAILS unless every link of it carries a recorded
|
|
16
|
-
* `chain_reason`);
|
|
17
|
-
* - same-shape merge candidates for the planner.
|
|
18
|
-
*
|
|
19
|
-
* Callers: `phase-plan-index` (src/phase.cts) adds this to the plan inventory.
|
|
20
|
-
* The planner and plan-checker agents read that output; they never compute a
|
|
21
|
-
* wave or a chain themselves.
|
|
22
|
-
*
|
|
23
|
-
* ## The wave rule
|
|
24
|
-
*
|
|
25
|
-
* A plan's wave is its longest-path level over two kinds of edges:
|
|
26
|
-
* 1. declared `depends_on` edges;
|
|
27
|
-
* 2. implicit edges between two plans that share a SOURCE file (see
|
|
28
|
-
* `classifyPlanFile`) and are not already ordered by (1). The implicit
|
|
29
|
-
* edge runs from the earlier plan (file order) to the later one.
|
|
30
|
-
* Two plans that share only NON-source files (a test file, a size baseline, a
|
|
31
|
-
* changeset, a doc, a planning file) get no edge: they stay in the same wave
|
|
32
|
-
* and are put in one serial group instead.
|
|
33
|
-
*/
|
|
34
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.PLAN_TASK_CAP = exports.PLAN_TITLE_MAX = void 0;
|
|
36
|
-
exports.classifyPlanFile = classifyPlanFile;
|
|
37
|
-
exports.isInstructionFile = isInstructionFile;
|
|
38
|
-
exports.isSourceFile = isSourceFile;
|
|
39
|
-
exports.planTitleProblem = planTitleProblem;
|
|
40
|
-
exports.readPlanTitle = readPlanTitle;
|
|
41
|
-
exports.computePlanGraph = computePlanGraph;
|
|
42
|
-
const SOURCE_DIRS = ['src/', 'bin/', 'pp-core/bin/', 'hooks/', 'scripts/'];
|
|
43
|
-
const DOC_EXTENSIONS = /\.(md|mdx|markdown|rst|txt|adoc)$/i;
|
|
44
|
-
const DOC_BASENAMES = /^(LICENSE|NOTICE|CHANGELOG|AUTHORS)(\.[a-z]+)?$/i;
|
|
45
|
-
function normalizePath(file) {
|
|
46
|
-
return String(file).trim().replace(/\\/g, '/').replace(/^\.\//, '');
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Classify one `files_modified` entry. This is THE source/non-source predicate
|
|
50
|
-
* for plan ordering; nothing else decides it.
|
|
51
|
-
*
|
|
52
|
-
* Checked in this order (first match wins):
|
|
53
|
-
* planning — under `.planning/`
|
|
54
|
-
* changeset — under `.changeset/` or `.changesets/`
|
|
55
|
-
* baseline — a `*baseline*.json`/`*.baseline.*` file, `__snapshots__/`, `*.snap`
|
|
56
|
-
* test — under a `test/`, `tests/` or `__tests__/` directory or a
|
|
57
|
-
* `*.Tests/` project; or named `*.test.*`, `*.spec.*`,
|
|
58
|
-
* `*_test.go|py`, `test_*.py`
|
|
59
|
-
* source — under `src/`, `bin/`, `pp-core/bin/`, `hooks/` or `scripts/`
|
|
60
|
-
* (pp-core ships executable code from these)
|
|
61
|
-
* doc — a `.md/.mdx/.markdown/.rst/.txt/.adoc` file, anything under
|
|
62
|
-
* `docs/` or `doc/`, or LICENSE/NOTICE/CHANGELOG/AUTHORS. In
|
|
63
|
-
* pp-core this includes workflow, agent, reference and template
|
|
64
|
-
* markdown.
|
|
65
|
-
* source — anything else. An unrecognised file is treated as source, so
|
|
66
|
-
* it keeps the strict "shared file = later wave" rule.
|
|
67
|
-
*/
|
|
68
|
-
function classifyPlanFile(file) {
|
|
69
|
-
const p = normalizePath(file);
|
|
70
|
-
const base = p.split('/').pop() ?? p;
|
|
71
|
-
if (p.startsWith('.planning/'))
|
|
72
|
-
return 'planning';
|
|
73
|
-
if (/^\.changesets?\//.test(p))
|
|
74
|
-
return 'changeset';
|
|
75
|
-
if (/baseline[^/]*\.json$/i.test(base) || /\.baseline\./i.test(base) || /(^|\/)__snapshots__\//.test(p) || /\.snap$/i.test(base)) {
|
|
76
|
-
return 'baseline';
|
|
77
|
-
}
|
|
78
|
-
if (/(^|\/)(tests?|__tests__)\//.test(p) ||
|
|
79
|
-
/(^|\/)[^/]+\.Tests?\//.test(p) ||
|
|
80
|
-
/\.(test|spec)\.[^/]+$/i.test(base) ||
|
|
81
|
-
/_test\.(go|py)$/i.test(base) ||
|
|
82
|
-
/^test_[^/]*\.py$/i.test(base)) {
|
|
83
|
-
return 'test';
|
|
84
|
-
}
|
|
85
|
-
if (SOURCE_DIRS.some((dir) => p.startsWith(dir)))
|
|
86
|
-
return 'source';
|
|
87
|
-
if (DOC_EXTENSIONS.test(base) || /^docs?\//.test(p) || DOC_BASENAMES.test(base))
|
|
88
|
-
return 'doc';
|
|
89
|
-
return 'source';
|
|
90
|
-
}
|
|
91
|
-
/** A workflow or agent markdown file (the unit same-shape plans are made of). */
|
|
92
|
-
function isInstructionFile(file) {
|
|
93
|
-
const p = normalizePath(file);
|
|
94
|
-
return /\.md$/i.test(p) && (/(^|\/)workflows\//.test(p) || /(^|\/)agents\//.test(p));
|
|
95
|
-
}
|
|
96
|
-
/** True when `file` is a source file for plan ordering (see `classifyPlanFile`). */
|
|
97
|
-
function isSourceFile(file) {
|
|
98
|
-
return classifyPlanFile(file) === 'source';
|
|
99
|
-
}
|
|
100
|
-
/** Longest a plan title may be before it is reported (still valid). */
|
|
101
|
-
exports.PLAN_TITLE_MAX = 100;
|
|
102
|
-
/**
|
|
103
|
-
* Problem with a plan's `title:` frontmatter value, or null when it is a valid
|
|
104
|
-
* one-line title. `undefined` means the key is absent.
|
|
105
|
-
*/
|
|
106
|
-
function planTitleProblem(value) {
|
|
107
|
-
if (value === undefined)
|
|
108
|
-
return 'Missing required frontmatter field: title';
|
|
109
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
110
|
-
return 'Frontmatter field title must be a non-empty one-line string';
|
|
111
|
-
}
|
|
112
|
-
if (/[\r\n]/.test(value))
|
|
113
|
-
return 'Frontmatter field title must be one line';
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
/** Normalised title, or null when absent/invalid. */
|
|
117
|
-
function readPlanTitle(value) {
|
|
118
|
-
return planTitleProblem(value) === null ? value.trim() : null;
|
|
119
|
-
}
|
|
120
|
-
/** Default task cap per plan (agents/pp-planner.md: "Each plan: 2-3 tasks maximum."). */
|
|
121
|
-
exports.PLAN_TASK_CAP = 3;
|
|
122
|
-
function slug(text) {
|
|
123
|
-
return text.toLowerCase().replace(/\.[a-z0-9]+$/i, '').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'group';
|
|
124
|
-
}
|
|
125
|
-
function intersect(a, b) {
|
|
126
|
-
return [...new Set(a.filter((f) => b.has(f)))].sort();
|
|
127
|
-
}
|
|
128
|
-
/**
|
|
129
|
-
* Compute the plan graph. `nodes` must be in plan file order (that order
|
|
130
|
-
* breaks ties and orients implicit edges).
|
|
131
|
-
*/
|
|
132
|
-
function computePlanGraph(nodes, opts = {}) {
|
|
133
|
-
const taskCap = opts.taskCap ?? exports.PLAN_TASK_CAP;
|
|
134
|
-
const ids = nodes.map((n) => n.id);
|
|
135
|
-
const index = new Map(ids.map((id, i) => [id, i]));
|
|
136
|
-
const files = nodes.map((n) => n.filesModified.map(normalizePath).filter((f) => f !== ''));
|
|
137
|
-
const fileSets = files.map((f) => new Set(f));
|
|
138
|
-
const warnings = [];
|
|
139
|
-
// Adjacency over indices: edges[i] = successors of i.
|
|
140
|
-
const succ = nodes.map(() => new Set());
|
|
141
|
-
nodes.forEach((n, j) => {
|
|
142
|
-
for (const dep of n.dependsOn) {
|
|
143
|
-
const i = index.get(dep);
|
|
144
|
-
if (i === undefined || i === j)
|
|
145
|
-
continue;
|
|
146
|
-
succ[i].add(j);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
const levelsOf = () => {
|
|
150
|
-
const inDeg = nodes.map(() => 0);
|
|
151
|
-
succ.forEach((s) => s.forEach((j) => { inDeg[j]++; }));
|
|
152
|
-
const level = nodes.map(() => 0);
|
|
153
|
-
const queue = [];
|
|
154
|
-
inDeg.forEach((d, i) => { if (d === 0)
|
|
155
|
-
queue.push(i); });
|
|
156
|
-
let head = 0;
|
|
157
|
-
while (head < queue.length) {
|
|
158
|
-
const cur = queue[head++];
|
|
159
|
-
for (const nxt of [...succ[cur]].sort((a, b) => a - b)) {
|
|
160
|
-
level[nxt] = Math.max(level[nxt], level[cur] + 1);
|
|
161
|
-
if (--inDeg[nxt] === 0)
|
|
162
|
-
queue.push(nxt);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return { level, done: new Set(queue) };
|
|
166
|
-
};
|
|
167
|
-
// A declared cycle has no waves; nodes Kahn never dequeued are on or behind it.
|
|
168
|
-
const declaredLevels = levelsOf();
|
|
169
|
-
if (declaredLevels.done.size < nodes.length) {
|
|
170
|
-
return emptyGraph(nodes, ids.filter((_, i) => !declaredLevels.done.has(i)));
|
|
171
|
-
}
|
|
172
|
-
const reaches = (from, to) => {
|
|
173
|
-
const stack = [from];
|
|
174
|
-
const seen = new Set([from]);
|
|
175
|
-
while (stack.length > 0) {
|
|
176
|
-
const cur = stack.pop();
|
|
177
|
-
if (cur === to)
|
|
178
|
-
return true;
|
|
179
|
-
for (const nxt of succ[cur]) {
|
|
180
|
-
if (!seen.has(nxt)) {
|
|
181
|
-
seen.add(nxt);
|
|
182
|
-
stack.push(nxt);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return false;
|
|
187
|
-
};
|
|
188
|
-
// Implicit edges: a shared SOURCE file orders two otherwise-unordered plans.
|
|
189
|
-
const implicitEdges = [];
|
|
190
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
191
|
-
for (let j = i + 1; j < nodes.length; j++) {
|
|
192
|
-
const sharedSource = intersect(files[j], fileSets[i]).filter(isSourceFile);
|
|
193
|
-
if (sharedSource.length === 0)
|
|
194
|
-
continue;
|
|
195
|
-
if (reaches(i, j) || reaches(j, i))
|
|
196
|
-
continue;
|
|
197
|
-
succ[i].add(j);
|
|
198
|
-
implicitEdges.push({ from: ids[i], to: ids[j], shared_source_files: sharedSource });
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
const { level } = levelsOf();
|
|
202
|
-
// Same-shape merge candidates: no source file, exactly one workflow/agent file
|
|
203
|
-
// (any other files are tests, baselines, changesets or planning), same task shape.
|
|
204
|
-
const shapeGroups = new Map();
|
|
205
|
-
nodes.forEach((n, i) => {
|
|
206
|
-
if (n.taskTypes.length === 0)
|
|
207
|
-
return;
|
|
208
|
-
const kinds = files[i].map(classifyPlanFile);
|
|
209
|
-
if (kinds.includes('source'))
|
|
210
|
-
return;
|
|
211
|
-
if (files[i].filter(isInstructionFile).length !== 1 || kinds.filter((k) => k === 'doc').length !== 1)
|
|
212
|
-
return;
|
|
213
|
-
const shape = n.taskTypes.join('+');
|
|
214
|
-
if (!shapeGroups.has(shape))
|
|
215
|
-
shapeGroups.set(shape, []);
|
|
216
|
-
shapeGroups.get(shape).push(i);
|
|
217
|
-
});
|
|
218
|
-
const mergeCandidates = [];
|
|
219
|
-
for (const [shape, members] of shapeGroups) {
|
|
220
|
-
if (members.length < 2)
|
|
221
|
-
continue;
|
|
222
|
-
const totalTasks = members.reduce((sum, i) => sum + nodes[i].taskTypes.length, 0);
|
|
223
|
-
mergeCandidates.push({
|
|
224
|
-
shape,
|
|
225
|
-
plans: members.map((i) => ids[i]),
|
|
226
|
-
total_tasks: totalTasks,
|
|
227
|
-
task_cap: taskCap,
|
|
228
|
-
action: totalTasks <= taskCap ? 'merge' : 'serial_group',
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
// Serial groups: union-find within each level.
|
|
232
|
-
const parent = nodes.map((_, i) => i);
|
|
233
|
-
const find = (x) => (parent[x] === x ? x : (parent[x] = find(parent[x])));
|
|
234
|
-
const union = (a, b) => {
|
|
235
|
-
const ra = find(a);
|
|
236
|
-
const rb = find(b);
|
|
237
|
-
if (ra !== rb)
|
|
238
|
-
parent[Math.max(ra, rb)] = Math.min(ra, rb);
|
|
239
|
-
};
|
|
240
|
-
for (let i = 0; i < nodes.length; i++) {
|
|
241
|
-
for (let j = i + 1; j < nodes.length; j++) {
|
|
242
|
-
if (level[i] !== level[j])
|
|
243
|
-
continue;
|
|
244
|
-
const shared = intersect(files[j], fileSets[i]);
|
|
245
|
-
const sameDeclared = nodes[i].serialGroup !== null && nodes[i].serialGroup === nodes[j].serialGroup;
|
|
246
|
-
if (shared.length > 0 || sameDeclared)
|
|
247
|
-
union(i, j);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
for (const cand of mergeCandidates) {
|
|
251
|
-
if (cand.action !== 'serial_group')
|
|
252
|
-
continue;
|
|
253
|
-
const members = cand.plans.map((id) => index.get(id));
|
|
254
|
-
for (const a of members)
|
|
255
|
-
for (const b of members)
|
|
256
|
-
if (a < b && level[a] === level[b])
|
|
257
|
-
union(a, b);
|
|
258
|
-
}
|
|
259
|
-
const groupsByRoot = new Map();
|
|
260
|
-
nodes.forEach((_, i) => {
|
|
261
|
-
const r = find(i);
|
|
262
|
-
if (!groupsByRoot.has(r))
|
|
263
|
-
groupsByRoot.set(r, []);
|
|
264
|
-
groupsByRoot.get(r).push(i);
|
|
265
|
-
});
|
|
266
|
-
const serialGroupOf = Object.fromEntries(ids.map((id) => [id, null]));
|
|
267
|
-
const serialGroups = [];
|
|
268
|
-
const usedNames = new Set();
|
|
269
|
-
const roots = [...groupsByRoot.keys()].sort((a, b) => a - b);
|
|
270
|
-
for (const root of roots) {
|
|
271
|
-
const members = groupsByRoot.get(root).sort((a, b) => a - b);
|
|
272
|
-
if (members.length < 2)
|
|
273
|
-
continue;
|
|
274
|
-
const sharedFiles = new Set();
|
|
275
|
-
for (const a of members)
|
|
276
|
-
for (const b of members)
|
|
277
|
-
if (a < b)
|
|
278
|
-
intersect(files[b], fileSets[a]).forEach((f) => sharedFiles.add(f));
|
|
279
|
-
const shared = [...sharedFiles].sort();
|
|
280
|
-
const declaredName = members.map((i) => nodes[i].serialGroup).find((g) => !!g);
|
|
281
|
-
let name = declaredName ?? `w${level[root] + 1}-${shared.length > 0 ? slug(shared[0].split('/').pop()) : 'same-shape'}`;
|
|
282
|
-
if (usedNames.has(name)) {
|
|
283
|
-
let k = 2;
|
|
284
|
-
while (usedNames.has(`${name}-${k}`))
|
|
285
|
-
k++;
|
|
286
|
-
name = `${name}-${k}`;
|
|
287
|
-
}
|
|
288
|
-
usedNames.add(name);
|
|
289
|
-
for (const i of members)
|
|
290
|
-
serialGroupOf[ids[i]] = name;
|
|
291
|
-
serialGroups.push({ name, level: level[root], plans: members.map((i) => ids[i]), shared_files: shared });
|
|
292
|
-
}
|
|
293
|
-
nodes.forEach((n, i) => {
|
|
294
|
-
if (n.serialGroup && serialGroupOf[ids[i]] !== n.serialGroup) {
|
|
295
|
-
warnings.push(`Plan ${ids[i]}: declares serial_group: ${n.serialGroup} but the plan graph puts it in ` +
|
|
296
|
-
`${serialGroupOf[ids[i]] ?? 'no serial group'} (a serial group only holds plans of one wave).`);
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
// Longest chain (by plan count) over the full DAG.
|
|
300
|
-
const order = ids.map((_, i) => i).sort((a, b) => level[a] - level[b] || a - b);
|
|
301
|
-
const best = nodes.map(() => 1);
|
|
302
|
-
const prev = nodes.map(() => null);
|
|
303
|
-
const preds = nodes.map(() => []);
|
|
304
|
-
succ.forEach((s, i) => s.forEach((j) => preds[j].push(i)));
|
|
305
|
-
for (const j of order) {
|
|
306
|
-
for (const i of preds[j].sort((a, b) => a - b)) {
|
|
307
|
-
if (best[i] + 1 > best[j]) {
|
|
308
|
-
best[j] = best[i] + 1;
|
|
309
|
-
prev[j] = i;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
let end = -1;
|
|
314
|
-
nodes.forEach((_, i) => { if (end === -1 || best[i] > best[end])
|
|
315
|
-
end = i; });
|
|
316
|
-
const chain = [];
|
|
317
|
-
for (let cur = end; cur !== null && cur !== -1; cur = prev[cur])
|
|
318
|
-
chain.unshift(cur);
|
|
319
|
-
const links = [];
|
|
320
|
-
for (let k = 1; k < chain.length; k++) {
|
|
321
|
-
const a = chain[k - 1];
|
|
322
|
-
const b = chain[k];
|
|
323
|
-
const shared = intersect(files[b], fileSets[a]);
|
|
324
|
-
const kind = shared.some(isSourceFile) ? 'source-file' : shared.length > 0 ? 'non-source-file' : 'declared';
|
|
325
|
-
links.push({ from: ids[a], to: ids[b], kind, shared_files: shared, reason: nodes[b].chainReason });
|
|
326
|
-
}
|
|
327
|
-
const nonSourceLinks = links.filter((l) => l.kind === 'non-source-file').length;
|
|
328
|
-
const limit = nodes.length / 2;
|
|
329
|
-
const unreasoned = links.filter((l) => !l.reason).map((l) => ({ from: l.from, to: l.to, kind: l.kind }));
|
|
330
|
-
const passed = chain.length <= limit || unreasoned.length === 0;
|
|
331
|
-
const waveCount = nodes.length === 0 ? 0 : Math.max(...level) + 1;
|
|
332
|
-
const message = passed
|
|
333
|
-
? `longest chain ${chain.length} of ${nodes.length} plans (limit ${limit})` +
|
|
334
|
-
(chain.length > limit ? ' — every link has a chain_reason' : '')
|
|
335
|
-
: `longest chain is ${chain.length} plans, over half the plan count (${nodes.length} plans, limit ${limit}), ` +
|
|
336
|
-
`and ${unreasoned.length} link(s) have no chain_reason: ${unreasoned.map((l) => `${l.from}->${l.to} (${l.kind})`).join(', ')}. ` +
|
|
337
|
-
`Drop depends_on edges that exist only because two plans touch the same non-source file ` +
|
|
338
|
-
`(they share a wave in a serial group instead), merge same-shape plans, or record a ` +
|
|
339
|
-
`chain_reason on each later plan saying why it must wait.`;
|
|
340
|
-
return {
|
|
341
|
-
cycle: null,
|
|
342
|
-
level: Object.fromEntries(ids.map((id, i) => [id, level[i]])),
|
|
343
|
-
serialGroupOf,
|
|
344
|
-
serialGroups,
|
|
345
|
-
implicitEdges,
|
|
346
|
-
plan_count: nodes.length,
|
|
347
|
-
wave_count: waveCount,
|
|
348
|
-
longest_chain: { length: chain.length, plans: chain.map((i) => ids[i]), links, non_source_links: nonSourceLinks },
|
|
349
|
-
chain_check: { passed, limit, unreasoned_links: passed ? [] : unreasoned, message },
|
|
350
|
-
merge_candidates: mergeCandidates,
|
|
351
|
-
warnings,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
function emptyGraph(nodes, cycle) {
|
|
355
|
-
return {
|
|
356
|
-
cycle,
|
|
357
|
-
level: {},
|
|
358
|
-
serialGroupOf: Object.fromEntries(nodes.map((n) => [n.id, null])),
|
|
359
|
-
serialGroups: [],
|
|
360
|
-
implicitEdges: [],
|
|
361
|
-
plan_count: nodes.length,
|
|
362
|
-
wave_count: 0,
|
|
363
|
-
longest_chain: { length: 0, plans: [], links: [], non_source_links: 0 },
|
|
364
|
-
chain_check: { passed: false, limit: nodes.length / 2, unreasoned_links: [], message: `depends_on cycle: ${cycle.join(', ')}` },
|
|
365
|
-
merge_candidates: [],
|
|
366
|
-
warnings: [],
|
|
367
|
-
};
|
|
368
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# Planner — plan graph: waves, serial groups, chains, titles
|
|
2
|
-
|
|
3
|
-
Read this in `assign_waves`. The plan graph is computed by `pp_run query phase-plan-index <phase>`
|
|
4
|
-
(`src/plan-graph.cts`); you never hand-compute a wave or a chain. You write `depends_on`, `files_modified`,
|
|
5
|
-
`title`, and when needed `chain_reason`; the query computes the rest.
|
|
6
|
-
|
|
7
|
-
## What you write in `depends_on`
|
|
8
|
-
|
|
9
|
-
Only real dependencies: plan B needs something plan A creates (a type, an API, a file's new content it reads).
|
|
10
|
-
Do NOT add a `depends_on` edge because two plans touch the same file.
|
|
11
|
-
|
|
12
|
-
## Source vs non-source files
|
|
13
|
-
|
|
14
|
-
The query classifies each `files_modified` entry (first match wins):
|
|
15
|
-
|
|
16
|
-
| Kind | Files |
|
|
17
|
-
|------|-------|
|
|
18
|
-
| planning | `.planning/**` |
|
|
19
|
-
| changeset | `.changeset/**` |
|
|
20
|
-
| baseline | `*baseline*.json`, `*.baseline.*`, `__snapshots__/`, `*.snap` |
|
|
21
|
-
| test | `test/`, `tests/`, `__tests__/`, `*.Tests/` dirs; `*.test.*`, `*.spec.*`, `*_test.go`, `test_*.py` |
|
|
22
|
-
| source | `src/`, `bin/`, `pp-core/bin/`, `hooks/`, `scripts/` |
|
|
23
|
-
| doc | `.md`/`.mdx`/`.rst`/`.txt`/`.adoc`, `docs/`, LICENSE/NOTICE/CHANGELOG (in pp-core: workflow, agent and reference markdown) |
|
|
24
|
-
| source | anything else |
|
|
25
|
-
|
|
26
|
-
## The rules the query applies
|
|
27
|
-
|
|
28
|
-
1. **Shared source file → later wave.** Two plans that share a source file and are not already ordered by
|
|
29
|
-
`depends_on` get an implicit edge (earlier plan file first). Unchanged from the old rule.
|
|
30
|
-
2. **Shared non-source file only → same wave, one serial group.** Plans in the same wave that share only
|
|
31
|
-
tests, baselines, changesets, docs or planning files stay in that wave with a computed `serial_group`.
|
|
32
|
-
One executor runs a serial group's plans one after another, in plan order.
|
|
33
|
-
3. **Same-shape plans** (same task types, each on exactly one workflow/agent file, no source file) are listed
|
|
34
|
-
in `graph.merge_candidates`. `action: merge` → fold them into one plan (they fit the 3-task cap).
|
|
35
|
-
`action: serial_group` → keep them as plans; the query puts those in one wave in one serial group.
|
|
36
|
-
4. **Chain check.** `graph.chain_check.passed` is false when the longest chain is longer than half the plan
|
|
37
|
-
count and a link of it has no `chain_reason`. Fix it by dropping `depends_on` edges that exist only for a
|
|
38
|
-
shared non-source file, merging same-shape plans, or — when the order is real — writing on the later plan
|
|
39
|
-
`chain_reason: "<one line: what it needs from the plan before it>"`.
|
|
40
|
-
|
|
41
|
-
## Frontmatter you write back
|
|
42
|
-
|
|
43
|
-
After the query: set each plan's `wave:` to its computed `wave`, and `serial_group:` to its computed
|
|
44
|
-
`serial_group` (omit the key when null). Use `pp_run query frontmatter.set <plan> wave <N>` /
|
|
45
|
-
`... serial_group <name>`.
|
|
46
|
-
|
|
47
|
-
## Print before you return
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
Plan graph: {plan_count} plans · {wave_count} waves · longest chain {longest_chain.length}
|
|
51
|
-
({longest_chain.non_source_links} linked only by a non-source file) · chain check {passed|FAILED}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Size baselines
|
|
55
|
-
|
|
56
|
-
Never list `tests/*-size-baseline.json` in `files_modified` or put a `size:baseline` step in a plan. Each commit
|
|
57
|
-
is checked against a per-commit budget (`size_budget.*` in config); execute-phase re-baselines once, in the
|
|
58
|
-
phase's closing commit (`--reason "phase NN close"`). A plan whose design needs more growth than the budget says
|
|
59
|
-
so in its task, and that task records it with `npm run size:baseline -- --reason "<why>"`.
|
|
60
|
-
|
|
61
|
-
## Titles
|
|
62
|
-
|
|
63
|
-
Every PLAN.md has `title:` — one line, what the plan delivers (e.g. `title: "Plan graph in phase-plan-index"`).
|
|
64
|
-
`verify.plan-structure` and `frontmatter.validate --schema plan` fail a plan without one. Plans that already
|
|
65
|
-
have a SUMMARY only warn. The inventory query prints the title; execute-phase's wave table and executor
|
|
66
|
-
brief use it.
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Checkpoint handling (execute-phase `checkpoint_handling` step)
|
|
2
|
-
|
|
3
|
-
Read by `execute-phase.md` when an executor returns a checkpoint (plans with
|
|
4
|
-
`autonomous: false`, or an auth gate). Extracted verbatim so the host loop stays
|
|
5
|
-
under its size ceiling; nothing here runs unless a checkpoint is returned.
|
|
6
|
-
|
|
7
|
-
**Auto-mode checkpoint handling:**
|
|
8
|
-
Read auto-advance config (chain flag OR user preference — same boolean as `check.auto-mode`):
|
|
9
|
-
```bash
|
|
10
|
-
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
11
|
-
AUTO_MODE=$(pp_run query check auto-mode --pick active 2>/dev/null || echo "false")
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
When executor returns a checkpoint AND (`UNATTENDED` or `AUTO_MODE`) is true (U-E1):
|
|
15
|
-
- **human-action**, or package legitimacy — ANY of `**Gate:** blocking-human` in the return (the executor copies the task's `gate` attribute there), `gate="blocking-human"`, or `what-built` begins `Package`; the last two are the fallback for a return that predates the `Gate:` line → hard stop: present to user (below).
|
|
16
|
-
- **human-verify** (other) → continuation with `{user_response}` = `"DEFERRED — not verified by a human (unattended run); record this checkpoint as not done in the SUMMARY and continue"`; log `⏭ Checkpoint deferred (recorded, not approved)`.
|
|
17
|
-
- **decision** → continuation with the option marked recommended, else the first; log `⚡ Auto-selected: [option]`.
|
|
18
|
-
|
|
19
|
-
**Standard flow (neither flag, human-action, or blocking-human):**
|
|
20
|
-
|
|
21
|
-
1. Spawn agent for checkpoint plan
|
|
22
|
-
2. Agent runs until checkpoint task or auth gate → returns structured state
|
|
23
|
-
3. Agent return includes: completed tasks table, current task + blocker, checkpoint type/details, what's awaited
|
|
24
|
-
4. **Present to user:**
|
|
25
|
-
```
|
|
26
|
-
## Checkpoint: [Type]
|
|
27
|
-
|
|
28
|
-
**Plan:** 03-03 Dashboard Layout
|
|
29
|
-
**Progress:** 2/3 tasks complete
|
|
30
|
-
|
|
31
|
-
[Checkpoint Details from agent return]
|
|
32
|
-
[Awaiting section from agent return]
|
|
33
|
-
```
|
|
34
|
-
5. User responds: "approved"/"done" | issue description | decision selection
|
|
35
|
-
6. **Spawn continuation agent (NOT resume)** using continuation-prompt.md template:
|
|
36
|
-
- `{completed_tasks_table}`: From checkpoint return
|
|
37
|
-
- `{resume_task_number}` + `{resume_task_name}`: Current task
|
|
38
|
-
- `{user_response}`: What user provided
|
|
39
|
-
- `{resume_instructions}`: Based on checkpoint type
|
|
40
|
-
7. Continuation agent verifies previous commits, continues from resume point
|
|
41
|
-
8. Repeat until plan completes or user stops
|
|
42
|
-
|
|
43
|
-
**Fresh agent, not resume:** resume breaks with parallel tool calls.
|
|
44
|
-
|
|
45
|
-
**Checkpoints in parallel waves:** Agent pauses and returns while other parallel agents may complete. Present checkpoint, spawn continuation, wait for all before next wave.
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# Install run — execute-phase step (heavy tier)
|
|
2
|
-
|
|
3
|
-
Runs only when `DISPATCH_PLAN.stages.verify` lists `verify-install` (heavy tier). Verification of a heavy
|
|
4
|
-
phase measures the installed product, not the repository: `phase.install-run` builds and packs the
|
|
5
|
-
package, installs the tarball into a temp project with the installer the user runs
|
|
6
|
-
(`pp-core --claude --local`, temp HOME), runs init there, then runs the phase's install checks against
|
|
7
|
-
the INSTALLED `pp-tools` — never the repository's. The checks are the phase's ROADMAP
|
|
8
|
-
`**Install check:**` line (backticked `pp-tools …` commands); without it, a default smoke set runs.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
_PP_SHIM_NAME="pp-tools.cjs"; _pp_root() { ( for _v in $(env | sed -n 's/^\(GIT_[A-Za-z0-9_]*\)=.*/\1/p'); do unset "$_v"; done; _c=$(git rev-parse --git-common-dir 2>/dev/null); if [ "${_c##*/}" = .git ] && cd "$_c/.." 2>/dev/null; then pwd -P; else git rev-parse --show-toplevel 2>/dev/null || pwd; fi ); }; pp_run() { case "$PP_TOOLS" in *.cjs) node "$PP_TOOLS" "$@";; *) "$PP_TOOLS" "$@";; esac; }; _PP_RUNTIME_ROOT="${RUNTIME_DIR:-$(_pp_root)}"; PP_TOOLS="${_PP_RUNTIME_ROOT}/pp-core/bin/${_PP_SHIM_NAME}"; if [ -f "$PP_TOOLS" ]; then :; elif [ -f "${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${_PP_RUNTIME_ROOT}/.codex/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="$HOME/.claude/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${HERMES_HOME:-$HOME/.hermes}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEX_HOME:-$HOME/.codex}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/pp-core/bin/${_PP_SHIM_NAME}"; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}" ]; then PP_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/pp-core/bin/${_PP_SHIM_NAME}"; elif command -v pp-tools >/dev/null 2>&1; then PP_TOOLS="$(command -v pp-tools)"; else echo "ERROR: pp-tools.cjs not found at $PP_TOOLS and pp-tools is not on PATH. Run: npx -y @pareto4/pp-core@latest --claude --local" >&2; exit 1; fi; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${PP_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${PP_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
|
|
12
|
-
INSTALL_RUN=$(pp_run query phase.install-run "${PHASE_NUMBER}")
|
|
13
|
-
INSTALL_VERDICT=$(printf '%s' "$INSTALL_RUN" | jq -r '.verdict')
|
|
14
|
-
INSTALL_RUN_FILE=$(printf '%s' "$INSTALL_RUN" | jq -r '.results_file')
|
|
15
|
-
echo "install run: ${INSTALL_VERDICT} — $(printf '%s' "$INSTALL_RUN" | jq -r '.reason')"
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
The results file (`{phase_dir}/{phase_num}-INSTALL-RUN.json`) holds the tarball name and version, every
|
|
19
|
-
command with its exit code and truncated output, `failures[]` and the verdict. Do not re-run the commands
|
|
20
|
-
yourself and do not edit the file.
|
|
21
|
-
|
|
22
|
-
- `pass` / `not_applicable` (not a pp-core package): continue to the verifier.
|
|
23
|
-
- `fail`: the phase cannot pass verification. Print each `failures[]` entry (stage, command, exit code).
|
|
24
|
-
|
|
25
|
-
When the verifier is spawned, add this line to its `<files_to_read>` block:
|
|
26
|
-
`- {INSTALL_RUN_FILE} (installed-product run — judge user-facing behaviour on it, not on the repo)`.
|
|
27
|
-
|
|
28
|
-
When the verifier is NOT listed and the verdict is `fail`: do not mark the phase passed. Present the
|
|
29
|
-
failures as the gaps and offer `/pp-plan-phase {phase} --gaps`.
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# Phase-close size re-baseline — execute-phase step
|
|
2
|
-
|
|
3
|
-
Plans never run `size:baseline`: each commit is checked against a per-commit size budget
|
|
4
|
-
(`size_budget.per_file_bytes` / `size_budget.per_commit_bytes`). The phase's growth is recorded once,
|
|
5
|
-
here, in the phase's closing commit, after every wave, the code-review fixes and the regression gate.
|
|
6
|
-
|
|
7
|
-
Applies only when the project defines a `size:baseline` npm script (pp-core's own repository). Elsewhere
|
|
8
|
-
this step does nothing.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
if node -e "process.exit((require('./package.json').scripts || {})['size:baseline'] ? 0 : 1)" 2>/dev/null; then
|
|
12
|
-
npm run size:baseline -- --reason "phase ${PHASE_NUMBER} close" || {
|
|
13
|
-
echo "size:baseline failed — fix before completing phase ${PHASE_NUMBER}" >&2; exit 1; }
|
|
14
|
-
CHANGED_BASELINES=$(git diff --name-only -- 'tests/*-size-baseline.json')
|
|
15
|
-
if [ -n "$CHANGED_BASELINES" ]; then
|
|
16
|
-
git add -- $CHANGED_BASELINES
|
|
17
|
-
git commit -m "chore(phase-${PHASE_NUMBER}): re-baseline workflow and agent sizes at phase close"
|
|
18
|
-
fi
|
|
19
|
-
fi
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
The `_rebaselines` entry this writes lists every file the phase grew, with its delta — the review record for
|
|
23
|
-
the phase's growth. Nothing to commit means the phase grew nothing past the baseline.
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Serial-group dispatch — execute-phase step 3 (`kind: serial-group` spawns)
|
|
2
|
-
|
|
3
|
-
A serial group is a set of plans in one wave that share only non-source files (a test file, a size
|
|
4
|
-
baseline, a changeset, a doc). `phase.dispatch-plan` gives the group ONE spawn with
|
|
5
|
-
`isolation: main-checkout` and its `plans[]` in run order. Spawn exactly one executor for it — never
|
|
6
|
-
one per plan, never in a worktree (no `isolation=` parameter). Main-checkout spawns run one at a time;
|
|
7
|
-
the wave's worktree spawns may run alongside.
|
|
8
|
-
|
|
9
|
-
Emit the plan-start heartbeat for the group's first plan, then dispatch (runs in a subagent — no output until the whole group returns; expected, not a freeze):
|
|
10
|
-
|
|
11
|
-
```text
|
|
12
|
-
Agent(
|
|
13
|
-
subagent_type="pp-executor",
|
|
14
|
-
description="Execute serial group {serial_group} of phase {phase_number}: {plan count} plans",
|
|
15
|
-
model="{executor_model}", # omit this line when executor_model == "inherit"
|
|
16
|
-
prompt="
|
|
17
|
-
<objective>
|
|
18
|
-
Execute these plans of phase {phase_number}-{phase_name} on the main checkout, one after another, in this order:
|
|
19
|
-
{for each entry of spawn.plans, numbered:}
|
|
20
|
-
{n}. {plan.id} — {plan.title} — ${PROJECT_ROOT}/{phase_dir}/{plan file} — touched tests: {plan.touched_tests or "none"} — build: {plan.build}
|
|
21
|
-
For each plan in turn: execute its tasks with their own atomic commits, write and commit its own SUMMARY.md, run its touched test files, then start the next plan.
|
|
22
|
-
</objective>
|
|
23
|
-
|
|
24
|
-
<dispatch tracking="orchestrator" suite="orchestrator" build="{spawn.build}" />
|
|
25
|
-
|
|
26
|
-
<serial_group_rules>
|
|
27
|
-
- One plan at a time, in the listed order. Each plan gets its own task commits and its own `{plan_id}-SUMMARY.md`, committed before the next plan starts.
|
|
28
|
-
- Tests: per plan, run only the test files that plan touches (listed above) and its narrower `<verify>` commands. Never the full suite — the orchestrator runs it once after the wave.
|
|
29
|
-
- Build: only before the tests of a plan whose `build` is true. When the group's `build` is false, run no build at all.
|
|
30
|
-
- Never call `state.*`, `roadmap.update-plan-progress` or `requirements.mark-complete`, and never stage STATE.md, ROADMAP.md or REQUIREMENTS.md: the orchestrator advances them once per wave.
|
|
31
|
-
- Never run `size:baseline`; the phase's closing commit re-baselines.
|
|
32
|
-
- Halt: when a plan cannot finish (verification still failing after repair, a Rule 4 stop, a checkpoint), stop the group at that plan — do not start the next one — and return `## GROUP HALTED` with `**Halted at:** {plan_id}`, the reason, the completed plans with their SUMMARY paths, and the plans not started.
|
|
33
|
-
- Otherwise return `## GROUP COMPLETE` with one `## PLAN COMPLETE` block per plan, in order.
|
|
34
|
-
</serial_group_rules>
|
|
35
|
-
|
|
36
|
-
<sequential_execution>
|
|
37
|
-
You are running as a SEQUENTIAL executor agent on the main working tree.
|
|
38
|
-
Use normal git commits (with hooks). Do NOT use --no-verify.
|
|
39
|
-
REQUIRED ORDER, per plan: Write SUMMARY.md → commit → only then any narration.
|
|
40
|
-
</sequential_execution>
|
|
41
|
-
|
|
42
|
-
<execution_context>
|
|
43
|
-
@~/.claude/pp-core/workflows/execute-plan.md
|
|
44
|
-
@~/.claude/pp-core/templates/summary.md
|
|
45
|
-
@~/.claude/pp-core/references/checkpoints.md
|
|
46
|
-
@~/.claude/pp-core/references/tdd.md
|
|
47
|
-
</execution_context>
|
|
48
|
-
|
|
49
|
-
<files_to_read>
|
|
50
|
-
MANDATORY: load every file listed here with the Read tool before you start the work — this block is your required reading.
|
|
51
|
-
First resolve repo root: `PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)`
|
|
52
|
-
- Every plan file listed in <objective> (read the next one again before starting it)
|
|
53
|
-
- ${PROJECT_ROOT}/.planning/PROJECT.md, ${PROJECT_ROOT}/.planning/STATE.md, ${PROJECT_ROOT}/.planning/config.json (if exists)
|
|
54
|
-
- ${PROJECT_ROOT}/${phase_dir}/*-CONTEXT.md and *-RESEARCH.md (if they exist)
|
|
55
|
-
- ${PROJECT_ROOT}/CLAUDE.md or ${PROJECT_ROOT}/.claude/CLAUDE.md, and project skills (if any)
|
|
56
|
-
</files_to_read>
|
|
57
|
-
|
|
58
|
-
${AGENT_SKILLS}
|
|
59
|
-
|
|
60
|
-
<success_criteria>
|
|
61
|
-
- [ ] Every listed plan executed in order, or the group halted with the halting plan named
|
|
62
|
-
- [ ] Each task committed individually; one committed SUMMARY.md per completed plan
|
|
63
|
-
- [ ] No tracking-file writes (the orchestrator advances them once per wave)
|
|
64
|
-
</success_criteria>
|
|
65
|
-
"
|
|
66
|
-
)
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**On return:**
|
|
70
|
-
- `## GROUP COMPLETE` → emit a plan-complete heartbeat per plan and go on to step 4 (spot-checks use the spawn's `expected_summaries`).
|
|
71
|
-
- `## GROUP HALTED` → the plans before the halt are done (their SUMMARY.md counts for this wave's suite run and tracking commit). Route the halting plan to step 7 (handle failures). The plans after it are not started; they stay incomplete, so a re-run of `phase.dispatch-plan --wave {N}` lists only them.
|
|
72
|
-
- A checkpoint at a plan → `checkpoint_handling`; once resolved, re-run `phase.dispatch-plan --wave {N}` and dispatch the remaining spawn.
|