@pareto4/pp-core 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +31 -0
- package/agents/pp-code-fixer.md +1 -1
- package/agents/pp-debug-session-manager.md +1 -1
- package/agents/pp-debugger.md +1 -1
- package/agents/pp-executor.md +20 -10
- package/agents/pp-intel-updater.md +1 -1
- package/agents/pp-phase-researcher.md +1 -1
- package/agents/pp-plan-checker.md +5 -5
- package/agents/pp-planner.md +10 -24
- package/agents/pp-project-researcher.md +1 -1
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-researcher.md +1 -1
- package/agents/pp-verifier.md +8 -4
- package/bin/install.js +429 -82
- package/commands/pp-autonomous.md +8 -6
- package/commands/pp-discovery-to-build.md +1 -1
- package/commands/pp-discovery.md +3 -2
- package/commands/pp-discuss-phase.md +2 -2
- package/commands/pp-execute-phase.md +3 -1
- package/commands/pp-graphify.md +5 -5
- package/commands/pp-import.md +1 -1
- package/commands/pp-map-codebase.md +1 -1
- package/commands/pp-ns-project.md +2 -3
- package/commands/pp-ns-workflow.md +1 -1
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-verify-work.md +2 -1
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-cursor-session-start.js +2 -2
- package/hooks/dist/pp-session-state.sh +1 -1
- package/hooks/dist/pp-statusline.js +21 -2
- package/hooks/pp-cursor-session-start.js +2 -2
- package/hooks/pp-session-state.sh +1 -1
- package/hooks/pp-statusline.js +21 -2
- package/package.json +18 -5
- package/pareto-skills/pp-project-setup/SKILL.md +5 -63
- package/pp-core/bin/detect-build-commands.sh +182 -0
- package/pp-core/bin/lib/capability-registry.cjs +65 -7
- package/pp-core/bin/lib/check-command-router.cjs +79 -38
- package/pp-core/bin/lib/clusters.cjs +2 -3
- package/pp-core/bin/lib/command-aliases.cjs +36 -0
- package/pp-core/bin/lib/command-arg-projection.cjs +99 -1
- package/pp-core/bin/lib/commands.cjs +21 -6
- package/pp-core/bin/lib/compose-bindings.cjs +70 -6
- package/pp-core/bin/lib/config-schema.cjs +16 -0
- package/pp-core/bin/lib/config.cjs +142 -27
- package/pp-core/bin/lib/core-utils.cjs +43 -7
- package/pp-core/bin/lib/discovery-input-check.cjs +218 -0
- package/pp-core/bin/lib/discovery-rebaseline.cjs +1372 -0
- package/pp-core/bin/lib/discovery-research-track.cjs +979 -0
- package/pp-core/bin/lib/discovery-rollup.cjs +476 -0
- package/pp-core/bin/lib/discovery-screens.cjs +1995 -0
- package/pp-core/bin/lib/discovery-sweep.cjs +390 -0
- package/pp-core/bin/lib/discovery-table.cjs +98 -0
- package/pp-core/bin/lib/dispatch-plan.cjs +472 -0
- package/pp-core/bin/lib/frontmatter.cjs +17 -2
- package/pp-core/bin/lib/init-command-router.cjs +8 -5
- package/pp-core/bin/lib/init-contract.cjs +93 -4
- package/pp-core/bin/lib/init.cjs +124 -134
- package/pp-core/bin/lib/install-profiles.cjs +5 -0
- package/pp-core/bin/lib/install-run.cjs +536 -0
- package/pp-core/bin/lib/io.cjs +15 -0
- package/pp-core/bin/lib/loop-resolver.cjs +24 -0
- package/pp-core/bin/lib/milestone.cjs +345 -48
- package/pp-core/bin/lib/phase-command-router.cjs +23 -1
- package/pp-core/bin/lib/phase-locator.cjs +15 -6
- package/pp-core/bin/lib/phase-scope.cjs +91 -0
- package/pp-core/bin/lib/phase-tier.cjs +467 -0
- package/pp-core/bin/lib/phase.cjs +247 -21
- package/pp-core/bin/lib/phases-command-router.cjs +2 -0
- package/pp-core/bin/lib/plan-graph.cjs +368 -0
- package/pp-core/bin/lib/planning-workspace.cjs +53 -0
- package/pp-core/bin/lib/platform-bindings.cjs +32 -2
- package/pp-core/bin/lib/profile-output.cjs +11 -3
- package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
- package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
- package/pp-core/bin/lib/roadmap.cjs +141 -39
- package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
- package/pp-core/bin/lib/state-command-router.cjs +82 -13
- package/pp-core/bin/lib/state-document.cjs +124 -7
- package/pp-core/bin/lib/state.cjs +784 -257
- package/pp-core/bin/lib/template.cjs +22 -6
- package/pp-core/bin/lib/vault-command-router.cjs +57 -6
- package/pp-core/bin/lib/vault-practices.cjs +6 -36
- package/pp-core/bin/lib/vault-write-back.cjs +20 -4
- package/pp-core/bin/lib/vault.cjs +555 -23
- package/pp-core/bin/lib/verify.cjs +53 -31
- package/pp-core/bin/lib/workstream.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +26 -15
- package/pp-core/bin/portable-timeout.sh +89 -0
- package/pp-core/bin/pp-tools.cjs +149 -9
- package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
- package/pp-core/bin/shared/config-schema.manifest.json +3 -0
- package/pp-core/references/autonomous-best-effort.md +461 -0
- package/pp-core/references/autonomous-lifecycle.md +146 -0
- package/pp-core/references/autonomous-smart-discuss.md +16 -12
- package/pp-core/references/checkpoints.md +1 -1
- package/pp-core/references/discovery-id-conventions.md +202 -0
- package/pp-core/references/discovery-rebaseline.md +254 -0
- package/pp-core/references/discovery-research-track.md +283 -0
- package/pp-core/references/discovery-traceability.md +400 -0
- package/pp-core/references/loop-hook-dispatch.md +7 -0
- package/pp-core/references/mandatory-initial-read.md +1 -1
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-human-verify-mode.md +1 -1
- package/pp-core/references/planner-load-graph-context.md +1 -1
- package/pp-core/references/planner-plan-graph.md +66 -0
- package/pp-core/references/planner-tdd.md +1 -0
- package/pp-core/references/planning-config.md +8 -0
- package/pp-core/references/sketch-tooling.md +4 -0
- package/pp-core/references/tdd.md +1 -0
- package/pp-core/templates/claude-md.md +1 -1
- package/pp-core/templates/phase-prompt.md +6 -2
- package/pp-core/templates/prototype-review/review.js +303 -0
- package/pp-core/templates/state.md +6 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-phase.md +1 -1
- package/pp-core/workflows/add-tests.md +1 -1
- package/pp-core/workflows/add-todo.md +1 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -2
- package/pp-core/workflows/audit-fix.md +12 -16
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/audit-uat.md +1 -1
- package/pp-core/workflows/autonomous.md +68 -306
- package/pp-core/workflows/check-todos.md +1 -1
- package/pp-core/workflows/cleanup.md +1 -1
- package/pp-core/workflows/code-review-fix.md +1 -1
- package/pp-core/workflows/code-review.md +19 -7
- package/pp-core/workflows/complete-milestone.md +41 -60
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
- package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
- package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
- package/pp-core/workflows/discovery-sprint.md +79 -1714
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
- package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
- package/pp-core/workflows/discovery-to-build.md +21 -238
- package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
- package/pp-core/workflows/discuss-phase.md +9 -2
- package/pp-core/workflows/do.md +4 -4
- package/pp-core/workflows/docs-update.md +1 -1
- package/pp-core/workflows/edit-phase.md +1 -1
- package/pp-core/workflows/eval-review.md +1 -1
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
- package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
- package/pp-core/workflows/execute-phase.md +102 -216
- package/pp-core/workflows/execute-plan.md +6 -2
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/extract-learnings.md +1 -1
- package/pp-core/workflows/forensics.md +4 -4
- package/pp-core/workflows/graduation.md +1 -1
- package/pp-core/workflows/health.md +1 -1
- package/pp-core/workflows/help/modes/brief.md +1 -0
- package/pp-core/workflows/help/modes/default.md +2 -1
- package/pp-core/workflows/help/modes/full.md +12 -5
- package/pp-core/workflows/import.md +1 -1
- package/pp-core/workflows/ingest-docs.md +1 -1
- package/pp-core/workflows/init.md +18 -2
- package/pp-core/workflows/insert-phase.md +1 -1
- package/pp-core/workflows/list-workspaces.md +1 -1
- package/pp-core/workflows/manager.md +3 -3
- package/pp-core/workflows/map-codebase.md +2 -2
- package/pp-core/workflows/milestone-summary.md +4 -4
- package/pp-core/workflows/mvp-phase.md +1 -1
- package/pp-core/workflows/new-milestone.md +7 -8
- package/pp-core/workflows/new-project.md +6 -1
- package/pp-core/workflows/new-workspace.md +1 -1
- package/pp-core/workflows/next.md +2 -2
- package/pp-core/workflows/note.md +2 -2
- package/pp-core/workflows/pause-work.md +1 -1
- package/pp-core/workflows/plan-milestone-gaps.md +1 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
- package/pp-core/workflows/plan-phase.md +43 -167
- package/pp-core/workflows/plan-review-convergence.md +2 -2
- package/pp-core/workflows/plant-seed.md +1 -1
- package/pp-core/workflows/pr-branch.md +1 -1
- package/pp-core/workflows/profile-user.md +1 -1
- package/pp-core/workflows/progress.md +3 -3
- package/pp-core/workflows/quick.md +3 -1
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/remove-workspace.md +1 -1
- package/pp-core/workflows/resume-project.md +6 -12
- package/pp-core/workflows/review.md +1 -1
- package/pp-core/workflows/scan.md +1 -1
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/settings-advanced.md +1 -1
- package/pp-core/workflows/settings-integrations.md +1 -1
- package/pp-core/workflows/settings.md +1 -1
- package/pp-core/workflows/ship.md +25 -8
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +2 -1
- package/pp-core/workflows/spec-phase.md +1 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +1 -1
- package/pp-core/workflows/stats.md +2 -2
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/transition.md +5 -9
- package/pp-core/workflows/ui-phase.md +23 -2
- package/pp-core/workflows/ui-review.md +1 -1
- package/pp-core/workflows/ultraplan-phase.md +2 -2
- package/pp-core/workflows/validate-phase.md +1 -1
- package/pp-core/workflows/vault-write-back.md +1 -1
- package/pp-core/workflows/verify-phase.md +29 -26
- package/pp-core/workflows/verify-work.md +14 -10
- package/scripts/base64-scan.sh +7 -41
- package/scripts/changeset/cli.cjs +25 -5
- package/scripts/dev-env.cjs +462 -0
- package/scripts/lib/allowlist-ratchet.cjs +103 -1
- package/scripts/lib/size-budget.cjs +311 -0
- package/scripts/lint-neutrality-names.cjs +694 -0
- package/scripts/lint-org-coordinates.allowlist.json +35 -0
- package/scripts/lint-org-coordinates.cjs +426 -0
- package/scripts/lint-skill-deps.cjs +48 -9
- package/scripts/lint-test-file-count.cjs +17 -2
- package/scripts/prompt-injection-scan.sh +41 -20
- package/scripts/sync-manifest-versions.cjs +41 -7
- package/scripts/sync-runtime-launcher.cjs +106 -1
- package/scripts/update-size-baseline.cjs +274 -30
- package/scripts/workflow-size.cjs +61 -9
- package/commands/pp-build-handoff.md +0 -46
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
|
@@ -0,0 +1,368 @@
|
|
|
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
|
+
}
|
|
@@ -72,6 +72,28 @@ function planningDir(cwd, ws, project) {
|
|
|
72
72
|
function planningRoot(cwd) {
|
|
73
73
|
return node_path_1.default.join(cwd, '.planning');
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* THE owner of "where the milestone record lives", given an already-resolved
|
|
77
|
+
* planning base (`planningDir(cwd)` / `planningPaths(cwd).planning`).
|
|
78
|
+
*
|
|
79
|
+
* Phase 12 CARRY-04 moved the milestone-record WRITER into the workstream (a
|
|
80
|
+
* milestone record belongs beside the ROADMAP it closes) but left the readers
|
|
81
|
+
* joining their own copy of the location, some of them at the repo root — so a
|
|
82
|
+
* workstream-archived milestone was invisible to `init.new-milestone`,
|
|
83
|
+
* `history-digest`, `phases list --include-archived` and every verb that
|
|
84
|
+
* resolves a phase through `findPhaseInternal`. Callers that hold a cwd should
|
|
85
|
+
* read `planningPaths(cwd).milestones` / `.milestonesArchive`; callers that
|
|
86
|
+
* already hold a planning base (verify.cjs, phase.cjs) call this directly.
|
|
87
|
+
* Nobody re-derives the two names (phase 12, CARRY-05).
|
|
88
|
+
*
|
|
89
|
+
* @param planningBase - absolute path to the .planning workspace root
|
|
90
|
+
*/
|
|
91
|
+
function milestonePathsFor(planningBase) {
|
|
92
|
+
return {
|
|
93
|
+
milestones: node_path_1.default.join(planningBase, 'MILESTONES.md'),
|
|
94
|
+
milestonesArchive: node_path_1.default.join(planningBase, 'milestones'),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
75
97
|
function planningPaths(cwd, ws) {
|
|
76
98
|
const base = planningDir(cwd, ws);
|
|
77
99
|
return {
|
|
@@ -82,8 +104,37 @@ function planningPaths(cwd, ws) {
|
|
|
82
104
|
config: node_path_1.default.join(base, 'config.json'),
|
|
83
105
|
phases: node_path_1.default.join(base, 'phases'),
|
|
84
106
|
requirements: node_path_1.default.join(base, 'REQUIREMENTS.md'),
|
|
107
|
+
...milestonePathsFor(base),
|
|
85
108
|
};
|
|
86
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* THE owner of "which PROJECT.md answers for this cwd" — for READS and
|
|
112
|
+
* existence checks (quick 261007-jkl, D1; todo "init project_exists is root
|
|
113
|
+
* hardcoded").
|
|
114
|
+
*
|
|
115
|
+
* PROJECT.md is a SHARED file by design: migrateToWorkstreams (workstream.cts)
|
|
116
|
+
* leaves it at the project level when it moves ROADMAP/STATE/phases into
|
|
117
|
+
* .planning/workstreams/<name>/. So under a workstream the answer is the
|
|
118
|
+
* workstream's OWN PROJECT.md when one exists, else the SHARED one:
|
|
119
|
+
* own = planningPaths(cwd).project — honours GSD_WORKSTREAM and GSD_PROJECT
|
|
120
|
+
* shared = planningDir(cwd, null)/PROJECT.md — workstream segment absent,
|
|
121
|
+
* GSD_PROJECT still honoured
|
|
122
|
+
* When neither exists the shared path is named, which is where /pp-new-project
|
|
123
|
+
* creates it. In flat mode own === shared. Both candidates come from
|
|
124
|
+
* planningDir, so a traversing GSD_WORKSTREAM / GSD_PROJECT throws before any
|
|
125
|
+
* path is named.
|
|
126
|
+
*
|
|
127
|
+
* planningPaths().project is deliberately left as it is: it is the write
|
|
128
|
+
* target for callers that create or rewrite the workstream's own file.
|
|
129
|
+
*/
|
|
130
|
+
function resolveProjectFile(cwd) {
|
|
131
|
+
const own = planningPaths(cwd).project;
|
|
132
|
+
const shared = node_path_1.default.join(planningDir(cwd, null), 'PROJECT.md');
|
|
133
|
+
if (own !== shared && node_fs_1.default.existsSync(own)) {
|
|
134
|
+
return { path: own, exists: true, scope: 'workstream' };
|
|
135
|
+
}
|
|
136
|
+
return { path: shared, exists: node_fs_1.default.existsSync(shared), scope: 'shared' };
|
|
137
|
+
}
|
|
87
138
|
/**
|
|
88
139
|
* @param cwd
|
|
89
140
|
* @param fn - callback to run while holding the lock
|
|
@@ -238,6 +289,8 @@ module.exports = {
|
|
|
238
289
|
planningDir,
|
|
239
290
|
planningRoot,
|
|
240
291
|
planningPaths,
|
|
292
|
+
resolveProjectFile,
|
|
293
|
+
milestonePathsFor,
|
|
241
294
|
withPlanningLock,
|
|
242
295
|
getActiveWorkstream,
|
|
243
296
|
setActiveWorkstream,
|
|
@@ -34,8 +34,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
35
|
};
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.MACHINE_SPECIFIC_CLONE_TARGET_PATTERNS = exports.VAULT_REPO_PATTERN_SOURCE = exports.PLATFORM_REPO_PATTERN_SOURCE = exports.CONTRACT_SCHEMA_VERSION = void 0;
|
|
37
|
+
exports.BINDINGS_COORDINATE_FIELDS = exports.MACHINE_SPECIFIC_CLONE_TARGET_PATTERNS = exports.VAULT_REPO_PATTERN_SOURCE = exports.PLATFORM_REPO_PATTERN_SOURCE = exports.CONTRACT_SCHEMA_VERSION = void 0;
|
|
38
38
|
exports.validateBindings = validateBindings;
|
|
39
|
+
exports.redactBindingsForEmission = redactBindingsForEmission;
|
|
39
40
|
exports.cmdPlatformBindings = cmdPlatformBindings;
|
|
40
41
|
exports.cmdValidateBindings = cmdValidateBindings;
|
|
41
42
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -208,6 +209,33 @@ function validateBindings(o) {
|
|
|
208
209
|
}
|
|
209
210
|
return { valid: errors.length === 0, errors, warnings };
|
|
210
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Pack 6 D-07: the `.platform-bindings.json` fields that carry a remote repo
|
|
214
|
+
* coordinate (and so the fields a credential can ride in). Hosts are not
|
|
215
|
+
* listed: a host is a bare host by contract and is read for real use.
|
|
216
|
+
*/
|
|
217
|
+
exports.BINDINGS_COORDINATE_FIELDS = Object.freeze(['platform.repo', 'client.vaultRepo']);
|
|
218
|
+
/**
|
|
219
|
+
* Emission-only: a copy of a bindings-shaped object with every coordinate field
|
|
220
|
+
* routed through the one owner (`redactCloneTarget`, src/vault.cts — lazily
|
|
221
|
+
* required so this module's load graph is unchanged). Never use the result for
|
|
222
|
+
* a write, a comparison, or a clone.
|
|
223
|
+
*/
|
|
224
|
+
function redactBindingsForEmission(obj) {
|
|
225
|
+
if (!isObject(obj))
|
|
226
|
+
return obj;
|
|
227
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
228
|
+
const { redactCloneTarget } = require('./vault.cjs');
|
|
229
|
+
const copy = JSON.parse(JSON.stringify(obj));
|
|
230
|
+
for (const field of exports.BINDINGS_COORDINATE_FIELDS) {
|
|
231
|
+
const [ns, key] = field.split('.');
|
|
232
|
+
const parent = copy[ns];
|
|
233
|
+
if (isObject(parent) && typeof parent[key] === 'string') {
|
|
234
|
+
parent[key] = redactCloneTarget(parent[key]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return copy;
|
|
238
|
+
}
|
|
211
239
|
/**
|
|
212
240
|
* Reader handler for `pp-tools query platform-bindings`.
|
|
213
241
|
*
|
|
@@ -254,7 +282,9 @@ function cmdPlatformBindings(cwd, args, raw) {
|
|
|
254
282
|
return;
|
|
255
283
|
}
|
|
256
284
|
// present + valid → echo the bindings under a present flag.
|
|
257
|
-
|
|
285
|
+
// Pack 6 D-07: the remote coordinate fields are emitted display-safe; every
|
|
286
|
+
// other field is verbatim. The file on disk keeps the raw value.
|
|
287
|
+
output(Object.assign({ present: true }, redactBindingsForEmission(parsed)), raw);
|
|
258
288
|
}
|
|
259
289
|
// Anchored regex for `?ref=` values in module source lines (T-07-04: untrusted text,
|
|
260
290
|
// no YAML dependency, no shell-eval).
|
|
@@ -28,7 +28,12 @@ const { loadConfig } = configLoader;
|
|
|
28
28
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
29
29
|
const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
|
|
30
30
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
31
|
+
// The start-command fallback uses the pp-named alias (same function).
|
|
32
|
+
const runtime_slash_cjs_2 = require("./runtime-slash.cjs");
|
|
31
33
|
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-workspace.cjs is an export= CommonJS module
|
|
35
|
+
const planningWorkspace = require("./planning-workspace.cjs");
|
|
36
|
+
const { resolveProjectFile } = planningWorkspace;
|
|
32
37
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
33
38
|
const DIMENSION_KEYS = [
|
|
34
39
|
'communication_style', 'decision_speed', 'explanation_depth',
|
|
@@ -189,7 +194,7 @@ const CLAUDE_INSTRUCTIONS = {
|
|
|
189
194
|
// depends on the runtime resolved from the project's config/env.
|
|
190
195
|
function buildClaudeMdFallbacks(runtime) {
|
|
191
196
|
return {
|
|
192
|
-
project: `Project not yet initialized. Run ${String((0,
|
|
197
|
+
project: `Project not yet initialized. Run ${String((0, runtime_slash_cjs_2.formatPpSlash)('init', runtime))} to set up.`,
|
|
193
198
|
stack: 'Technology stack not yet documented. Will populate after codebase mapping or first phase.',
|
|
194
199
|
conventions: 'Conventions not yet established. Will populate as patterns emerge during development.',
|
|
195
200
|
architecture: 'Architecture not yet mapped. Follow existing patterns found in the codebase.',
|
|
@@ -300,7 +305,9 @@ function extractMarkdownSection(content, sectionName) {
|
|
|
300
305
|
// ─── CLAUDE.md Section Generators ─────────────────────────────────────────────
|
|
301
306
|
function generateProjectSection(cwd) {
|
|
302
307
|
const fallbacks = buildClaudeMdFallbacks((0, runtime_slash_cjs_1.resolveRuntime)(cwd));
|
|
303
|
-
|
|
308
|
+
// D1 (261007-jkl): the PROJECT.md that answers for this cwd (workstream's own, else
|
|
309
|
+
// the shared one; GSD_PROJECT honoured) — this used to be a fixed repo-root join.
|
|
310
|
+
const projectPath = resolveProjectFile(cwd).path;
|
|
304
311
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(projectPath);
|
|
305
312
|
if (!content) {
|
|
306
313
|
return { content: fallbacks['project'], source: 'PROJECT.md', linkPath: null, hasFallback: true };
|
|
@@ -330,7 +337,8 @@ function generateProjectSection(cwd) {
|
|
|
330
337
|
if (parts.length === 0) {
|
|
331
338
|
return { content: fallbacks['project'], source: 'PROJECT.md', linkPath: null, hasFallback: true };
|
|
332
339
|
}
|
|
333
|
-
|
|
340
|
+
const linkPath = node_path_1.default.relative(cwd, projectPath).split(node_path_1.default.sep).join('/');
|
|
341
|
+
return { content: parts.join('\n\n'), source: 'PROJECT.md', linkPath, hasFallback: false };
|
|
334
342
|
}
|
|
335
343
|
function generateStackSection(cwd) {
|
|
336
344
|
const fallbacks = buildClaudeMdFallbacks((0, runtime_slash_cjs_1.resolveRuntime)(cwd));
|