@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,536 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Install run — the single owner of heavy-tier "measure the installed
|
|
4
|
+
* product" verification (pack 9, item 5).
|
|
5
|
+
*
|
|
6
|
+
* For a pp-core package (a package.json whose `bin` has both `pp-core` and
|
|
7
|
+
* `pp-tools`), `phase.install-run <phase>`:
|
|
8
|
+
*
|
|
9
|
+
* 1. builds (`npm run build`, unless `--no-build`) and packs the package
|
|
10
|
+
* (`npm pack`, whose `prepack` compiles the shipped `.cjs`) — or takes a
|
|
11
|
+
* tarball given with `--tarball`;
|
|
12
|
+
* 2. installs the tarball into a temp npm prefix and runs the installer the
|
|
13
|
+
* user runs (`pp-core --claude --local`) in a temp fixture project, under
|
|
14
|
+
* a temp HOME — reusing the release smoke harness
|
|
15
|
+
* (scripts/release-tarball-smoke.cjs: `runSmoke`, `driveInstaller`);
|
|
16
|
+
* 3. runs init non-interactively through the INSTALLED pp-tools
|
|
17
|
+
* (`query init-contract --session-next/--session-answer`, the documented
|
|
18
|
+
* non-interactive form of `pp-tools init`);
|
|
19
|
+
* 4. runs the phase's install-check commands against the INSTALLED
|
|
20
|
+
* `.claude/pp-core/bin/pp-tools.cjs`, never the repository's;
|
|
21
|
+
* 5. writes `<phase_dir>/<NN>-INSTALL-RUN.json`: tarball name and version,
|
|
22
|
+
* every command with its exit code and truncated stdout/stderr, and a
|
|
23
|
+
* verdict (`pass` / `fail` / `not_applicable`).
|
|
24
|
+
*
|
|
25
|
+
* The install-check commands have ONE owner: the phase's ROADMAP entry, as an
|
|
26
|
+
* `**Install check:**` line of backticked `pp-tools …` commands (the same
|
|
27
|
+
* owner as the `**Tier:**` line). Without the line, `DEFAULT_INSTALL_CHECKS`
|
|
28
|
+
* run.
|
|
29
|
+
*
|
|
30
|
+
* npm runs offline first (`npm_config_offline`, from the caller's npm cache);
|
|
31
|
+
* on a cache miss it retries with `prefer-offline`, which may fetch the
|
|
32
|
+
* package's dependencies — the same network the existing install tests use.
|
|
33
|
+
* The registry is whatever the caller's npm configuration says; nothing here
|
|
34
|
+
* names one.
|
|
35
|
+
*/
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.OUTPUT_LIMIT = exports.INIT_ANSWERS = exports.DEFAULT_INSTALL_CHECKS = exports.INSTALL_RUN_SUFFIX = exports.INSTALL_RUN_SCHEMA = void 0;
|
|
41
|
+
exports.parseInstallCheckField = parseInstallCheckField;
|
|
42
|
+
exports.splitCommandLine = splitCommandLine;
|
|
43
|
+
exports.parseCheckCommand = parseCheckCommand;
|
|
44
|
+
exports.resolveInstallChecks = resolveInstallChecks;
|
|
45
|
+
exports.truncateOutput = truncateOutput;
|
|
46
|
+
exports.computeInstallVerdict = computeInstallVerdict;
|
|
47
|
+
exports.readTarballPackageJson = readTarballPackageJson;
|
|
48
|
+
exports.isPpCorePackage = isPpCorePackage;
|
|
49
|
+
exports.parseInstallRunArgs = parseInstallRunArgs;
|
|
50
|
+
exports.findSmokeHarness = findSmokeHarness;
|
|
51
|
+
exports.buildRunEnv = buildRunEnv;
|
|
52
|
+
exports.runInstallRun = runInstallRun;
|
|
53
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
54
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
55
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
56
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
57
|
+
const node_zlib_1 = __importDefault(require("node:zlib"));
|
|
58
|
+
const node_child_process_1 = require("node:child_process");
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- roadmap-parser.cjs is an export= CommonJS module
|
|
60
|
+
const roadmapParser = require("./roadmap-parser.cjs");
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
|
|
62
|
+
const phaseLocator = require("./phase-locator.cjs");
|
|
63
|
+
const { getRoadmapPhaseInternal } = roadmapParser;
|
|
64
|
+
const { findPhaseInternal } = phaseLocator;
|
|
65
|
+
exports.INSTALL_RUN_SCHEMA = 'pp-install-run/1';
|
|
66
|
+
exports.INSTALL_RUN_SUFFIX = 'INSTALL-RUN.json';
|
|
67
|
+
/** Run when the phase's ROADMAP entry has no `**Install check:**` line: the smoke job's surface. */
|
|
68
|
+
exports.DEFAULT_INSTALL_CHECKS = Object.freeze([
|
|
69
|
+
'pp-tools --help',
|
|
70
|
+
'pp-tools query config-get project.slug',
|
|
71
|
+
'pp-tools init phase-op 1',
|
|
72
|
+
'pp-tools progress',
|
|
73
|
+
]);
|
|
74
|
+
/** Fixed answers for the non-interactive init session in the fixture project. */
|
|
75
|
+
exports.INIT_ANSWERS = Object.freeze({
|
|
76
|
+
project_name: 'Install Run Fixture',
|
|
77
|
+
project_slug: 'install-run-fixture',
|
|
78
|
+
client_name: 'internal',
|
|
79
|
+
client_slug: 'internal',
|
|
80
|
+
description: 'Fixture project for the heavy-tier install run',
|
|
81
|
+
org_profile: 'none',
|
|
82
|
+
provider: 'none',
|
|
83
|
+
visibility: 'private',
|
|
84
|
+
});
|
|
85
|
+
exports.OUTPUT_LIMIT = 2000;
|
|
86
|
+
const INIT_MAX_STEPS = 30;
|
|
87
|
+
const COMMAND_TIMEOUT_MS = 120_000;
|
|
88
|
+
const BUILD_TIMEOUT_MS = 600_000;
|
|
89
|
+
const TOOL_NAMES = new Set(['pp-tools', 'pp_run']);
|
|
90
|
+
// ─── Pure parts ──────────────────────────────────────────────────────────────
|
|
91
|
+
const INSTALL_CHECK_RE = /^\*\*Install check(?::\*\*|\*\*:)[^\S\n]*([^\n]*)$/im;
|
|
92
|
+
/** The `**Install check:**` line of a ROADMAP phase section: its backticked commands, or null when absent. */
|
|
93
|
+
function parseInstallCheckField(section) {
|
|
94
|
+
if (!section)
|
|
95
|
+
return null;
|
|
96
|
+
const m = INSTALL_CHECK_RE.exec(section);
|
|
97
|
+
if (!m)
|
|
98
|
+
return null;
|
|
99
|
+
const commands = [...m[1].matchAll(/`([^`]+)`/g)].map((c) => c[1].trim()).filter(Boolean);
|
|
100
|
+
return { line: m[0].trim(), commands };
|
|
101
|
+
}
|
|
102
|
+
/** Split one command line into argv (single and double quotes, backslash escapes); no shell is involved. */
|
|
103
|
+
function splitCommandLine(text) {
|
|
104
|
+
const argv = [];
|
|
105
|
+
let cur = '';
|
|
106
|
+
let inToken = false;
|
|
107
|
+
let quote = null;
|
|
108
|
+
for (let i = 0; i < text.length; i++) {
|
|
109
|
+
const ch = text[i];
|
|
110
|
+
if (quote) {
|
|
111
|
+
if (ch === quote)
|
|
112
|
+
quote = null;
|
|
113
|
+
else if (ch === '\\' && quote === '"' && i + 1 < text.length)
|
|
114
|
+
cur += text[++i];
|
|
115
|
+
else
|
|
116
|
+
cur += ch;
|
|
117
|
+
}
|
|
118
|
+
else if (ch === '"' || ch === "'") {
|
|
119
|
+
quote = ch;
|
|
120
|
+
inToken = true;
|
|
121
|
+
}
|
|
122
|
+
else if (ch === '\\' && i + 1 < text.length) {
|
|
123
|
+
cur += text[++i];
|
|
124
|
+
inToken = true;
|
|
125
|
+
}
|
|
126
|
+
else if (/\s/.test(ch)) {
|
|
127
|
+
if (inToken)
|
|
128
|
+
argv.push(cur);
|
|
129
|
+
cur = '';
|
|
130
|
+
inToken = false;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
cur += ch;
|
|
134
|
+
inToken = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (quote)
|
|
138
|
+
return { error: `unterminated ${quote} quote` };
|
|
139
|
+
if (inToken)
|
|
140
|
+
argv.push(cur);
|
|
141
|
+
return argv;
|
|
142
|
+
}
|
|
143
|
+
/** A listed command must start with `pp-tools` (or `pp_run`); the rest is its argv. */
|
|
144
|
+
function parseCheckCommand(text) {
|
|
145
|
+
const split = splitCommandLine(text);
|
|
146
|
+
if (!Array.isArray(split))
|
|
147
|
+
return { text, error: split.error };
|
|
148
|
+
if (split.length === 0 || !TOOL_NAMES.has(split[0])) {
|
|
149
|
+
return { text, error: 'an install check must be a `pp-tools …` command (run against the installed copy)' };
|
|
150
|
+
}
|
|
151
|
+
return { text, argv: split.slice(1) };
|
|
152
|
+
}
|
|
153
|
+
/** The phase's install checks from its ROADMAP section, or the default smoke set. */
|
|
154
|
+
function resolveInstallChecks(section) {
|
|
155
|
+
const field = parseInstallCheckField(section);
|
|
156
|
+
const texts = field ? field.commands : [...exports.DEFAULT_INSTALL_CHECKS];
|
|
157
|
+
const commands = [];
|
|
158
|
+
const invalid = [];
|
|
159
|
+
for (const text of texts) {
|
|
160
|
+
const parsed = parseCheckCommand(text);
|
|
161
|
+
if ('error' in parsed)
|
|
162
|
+
invalid.push(parsed);
|
|
163
|
+
else
|
|
164
|
+
commands.push(parsed);
|
|
165
|
+
}
|
|
166
|
+
if (field && field.commands.length === 0) {
|
|
167
|
+
invalid.push({ text: field.line, error: 'the **Install check:** line lists no backticked commands' });
|
|
168
|
+
}
|
|
169
|
+
return { source: field ? 'roadmap' : 'default', line: field ? field.line : null, commands, invalid };
|
|
170
|
+
}
|
|
171
|
+
/** Keep the head and tail of long output. */
|
|
172
|
+
function truncateOutput(text, limit = exports.OUTPUT_LIMIT) {
|
|
173
|
+
const s = String(text ?? '');
|
|
174
|
+
if (s.length <= limit)
|
|
175
|
+
return s;
|
|
176
|
+
const half = Math.floor(limit / 2);
|
|
177
|
+
return `${s.slice(0, half)}\n… [${s.length - limit} chars truncated] …\n${s.slice(s.length - half)}`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The verdict: `not_applicable` when nothing installable was found; `fail`
|
|
181
|
+
* when any recorded command exited non-zero (or did not run), a listed check
|
|
182
|
+
* was invalid, or init did not finish; `pass` otherwise.
|
|
183
|
+
*/
|
|
184
|
+
function computeInstallVerdict(input) {
|
|
185
|
+
if (!input.applicable) {
|
|
186
|
+
return { verdict: 'not_applicable', reason: input.notApplicableReason || 'nothing installable', failures: [] };
|
|
187
|
+
}
|
|
188
|
+
const failures = input.commands
|
|
189
|
+
.filter((c) => c.exit_code !== 0)
|
|
190
|
+
.map((c) => ({ stage: c.stage, command: c.command, exit_code: c.exit_code, ...(c.error ? { error: c.error } : {}) }));
|
|
191
|
+
for (const bad of input.invalid) {
|
|
192
|
+
failures.push({ stage: 'check', command: bad.text, exit_code: null, error: bad.error });
|
|
193
|
+
}
|
|
194
|
+
if (!input.initDone && !failures.some((f) => f.stage !== 'check')) {
|
|
195
|
+
failures.push({ stage: 'init', command: 'pp-tools init', exit_code: null, error: 'the init session did not finish' });
|
|
196
|
+
}
|
|
197
|
+
if (!input.checksRun && failures.length === 0) {
|
|
198
|
+
failures.push({ stage: 'check', command: '(install checks)', exit_code: null, error: 'the install checks did not run' });
|
|
199
|
+
}
|
|
200
|
+
if (failures.length === 0)
|
|
201
|
+
return { verdict: 'pass', reason: 'every command exited 0 against the installed copy', failures };
|
|
202
|
+
const f = failures[0];
|
|
203
|
+
const how = f.exit_code === null ? (f.error || 'did not run') : `exited ${f.exit_code}`;
|
|
204
|
+
return { verdict: 'fail', reason: `${f.stage}: \`${f.command}\` ${how}`, failures };
|
|
205
|
+
}
|
|
206
|
+
/** `package/package.json` from an npm tarball (gzip + ustar), without a `tar` binary. */
|
|
207
|
+
function readTarballPackageJson(tarballPath) {
|
|
208
|
+
let buf;
|
|
209
|
+
try {
|
|
210
|
+
buf = node_zlib_1.default.gunzipSync(node_fs_1.default.readFileSync(tarballPath));
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
let off = 0;
|
|
216
|
+
let longName = null;
|
|
217
|
+
while (off + 512 <= buf.length) {
|
|
218
|
+
const header = buf.subarray(off, off + 512);
|
|
219
|
+
if (header.every((b) => b === 0))
|
|
220
|
+
break;
|
|
221
|
+
const field = (start, len) => header.subarray(start, start + len).toString('utf8').replace(/\0.*$/s, '');
|
|
222
|
+
const size = parseInt(field(124, 12).trim() || '0', 8) || 0;
|
|
223
|
+
const type = field(156, 1);
|
|
224
|
+
const prefix = field(345, 155);
|
|
225
|
+
const name = longName ?? (prefix ? `${prefix}/${field(0, 100)}` : field(0, 100));
|
|
226
|
+
longName = null;
|
|
227
|
+
const body = buf.subarray(off + 512, off + 512 + size);
|
|
228
|
+
if (type === 'L')
|
|
229
|
+
longName = body.toString('utf8').replace(/\0.*$/s, '');
|
|
230
|
+
else if (type === 'x') {
|
|
231
|
+
const m = /\d+ path=([^\n]*)\n/.exec(body.toString('utf8'));
|
|
232
|
+
if (m)
|
|
233
|
+
longName = m[1];
|
|
234
|
+
}
|
|
235
|
+
else if ((type === '0' || type === '') && name.replace(/^[^/]+\//, '') === 'package.json' && name.split('/').length === 2) {
|
|
236
|
+
try {
|
|
237
|
+
return JSON.parse(body.toString('utf8'));
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
off += 512 + Math.ceil(size / 512) * 512;
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
/** A pp-core package: its package.json `bin` names both `pp-core` (installer) and `pp-tools`. */
|
|
248
|
+
function isPpCorePackage(pkg) {
|
|
249
|
+
const bin = pkg && typeof pkg['bin'] === 'object' && pkg['bin'] !== null ? pkg['bin'] : null;
|
|
250
|
+
return !!bin && typeof bin['pp-core'] === 'string' && typeof bin['pp-tools'] === 'string';
|
|
251
|
+
}
|
|
252
|
+
function parseInstallRunArgs(args) {
|
|
253
|
+
const usage = 'Usage: phase install-run <phase> [--tarball <path.tgz>] [--package-dir <dir>] [--no-build]';
|
|
254
|
+
let phase = null;
|
|
255
|
+
let tarball = null;
|
|
256
|
+
let packageDir = null;
|
|
257
|
+
let build = true;
|
|
258
|
+
for (let i = 0; i < args.length; i++) {
|
|
259
|
+
const a = args[i];
|
|
260
|
+
if (a === '--raw')
|
|
261
|
+
continue;
|
|
262
|
+
if (a === '--no-build')
|
|
263
|
+
build = false;
|
|
264
|
+
else if (a === '--tarball' || a === '--package-dir') {
|
|
265
|
+
const v = args[i + 1];
|
|
266
|
+
if (!v || v.startsWith('--'))
|
|
267
|
+
return { error: `${a} requires a value. ${usage}` };
|
|
268
|
+
if (a === '--tarball')
|
|
269
|
+
tarball = v;
|
|
270
|
+
else
|
|
271
|
+
packageDir = v;
|
|
272
|
+
i++;
|
|
273
|
+
}
|
|
274
|
+
else if (a.startsWith('--'))
|
|
275
|
+
return { error: `phase install-run does not support ${a}. ${usage}` };
|
|
276
|
+
else if (phase === null)
|
|
277
|
+
phase = a;
|
|
278
|
+
else
|
|
279
|
+
return { error: `phase install-run takes one phase. ${usage}` };
|
|
280
|
+
}
|
|
281
|
+
if (phase === null)
|
|
282
|
+
return { error: `phase required for phase install-run. ${usage}` };
|
|
283
|
+
if (tarball && packageDir)
|
|
284
|
+
return { error: `--tarball and --package-dir are exclusive. ${usage}` };
|
|
285
|
+
return { phase, tarball, packageDir, build };
|
|
286
|
+
}
|
|
287
|
+
const HARNESS_REL = node_path_1.default.join('scripts', 'release-tarball-smoke.cjs');
|
|
288
|
+
/** The release smoke harness: the package's own first, then the running copy's. */
|
|
289
|
+
function findSmokeHarness(packageDir) {
|
|
290
|
+
const own = node_path_1.default.resolve(__dirname, '..', '..', '..');
|
|
291
|
+
for (const root of [packageDir, own]) {
|
|
292
|
+
if (!root)
|
|
293
|
+
continue;
|
|
294
|
+
const p = node_path_1.default.join(root, HARNESS_REL);
|
|
295
|
+
if (node_fs_1.default.existsSync(p))
|
|
296
|
+
return p;
|
|
297
|
+
}
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const npmCmd = () => (process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
301
|
+
function npmConfigGet(key, env) {
|
|
302
|
+
const r = (0, node_child_process_1.spawnSync)(npmCmd(), ['config', 'get', key], { encoding: 'utf-8', env, shell: process.platform === 'win32', timeout: 30_000, windowsHide: true });
|
|
303
|
+
const v = r.status === 0 ? String(r.stdout || '').trim() : '';
|
|
304
|
+
return v && v !== 'undefined' && v !== 'null' ? v : null;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* The env every child of the run sees: a temp HOME (and XDG config home), so
|
|
308
|
+
* nothing reads or writes the caller's runtime config dirs; the caller's npm
|
|
309
|
+
* cache and npm user config kept, so npm can install offline and reach the
|
|
310
|
+
* caller's configured registry.
|
|
311
|
+
*/
|
|
312
|
+
function buildRunEnv(base, home) {
|
|
313
|
+
const env = { ...base };
|
|
314
|
+
for (const k of Object.keys(env)) {
|
|
315
|
+
if (k.startsWith('GIT_') || k === 'GSD_TEST_MODE' || k === 'CLAUDE_CONFIG_DIR' || k === 'GSD_WORKSTREAM')
|
|
316
|
+
delete env[k];
|
|
317
|
+
}
|
|
318
|
+
const cache = base.npm_config_cache || npmConfigGet('cache', base);
|
|
319
|
+
if (cache)
|
|
320
|
+
env.npm_config_cache = cache;
|
|
321
|
+
env.npm_config_userconfig = base.npm_config_userconfig || node_path_1.default.join(node_os_1.default.homedir(), '.npmrc');
|
|
322
|
+
env.HOME = home;
|
|
323
|
+
env.USERPROFILE = home;
|
|
324
|
+
env.XDG_CONFIG_HOME = node_path_1.default.join(home, '.config');
|
|
325
|
+
env.npm_config_update_notifier = 'false';
|
|
326
|
+
env.npm_config_audit = 'false';
|
|
327
|
+
env.npm_config_fund = 'false';
|
|
328
|
+
env.npm_config_loglevel = 'error';
|
|
329
|
+
env.NO_UPDATE_NOTIFIER = '1';
|
|
330
|
+
return env;
|
|
331
|
+
}
|
|
332
|
+
const ANSI_RE = new RegExp('\\u001b\\[[0-9;]*[A-Za-z]', 'g');
|
|
333
|
+
/** Drop ANSI colour codes and replace the temp root with `<install-run>`. */
|
|
334
|
+
function scrub(text, root) {
|
|
335
|
+
const s = String(text ?? '').replace(ANSI_RE, '');
|
|
336
|
+
return root ? s.split(root).join('<install-run>') : s;
|
|
337
|
+
}
|
|
338
|
+
function record(out, root, stage, command, res, started) {
|
|
339
|
+
const entry = {
|
|
340
|
+
stage,
|
|
341
|
+
command,
|
|
342
|
+
exit_code: res.status,
|
|
343
|
+
stdout: truncateOutput(scrub(res.stdout ?? '', root)),
|
|
344
|
+
stderr: truncateOutput(scrub(res.stderr ?? '', root)),
|
|
345
|
+
duration_ms: Date.now() - started,
|
|
346
|
+
...(res.error ? { error: scrub(res.error, root) } : {}),
|
|
347
|
+
};
|
|
348
|
+
out.push(entry);
|
|
349
|
+
return entry;
|
|
350
|
+
}
|
|
351
|
+
function spawnRecorded(out, root, stage, command, file, args, opts) {
|
|
352
|
+
const started = Date.now();
|
|
353
|
+
const r = (0, node_child_process_1.spawnSync)(file, args, {
|
|
354
|
+
cwd: opts.cwd, env: opts.env, encoding: 'utf-8', timeout: opts.timeout ?? COMMAND_TIMEOUT_MS,
|
|
355
|
+
stdio: ['pipe', 'pipe', 'pipe'], shell: opts.shell ?? false, windowsHide: true,
|
|
356
|
+
});
|
|
357
|
+
const error = r.error ? String(r.error.message || r.error) : (r.signal ? `terminated by ${r.signal}` : undefined);
|
|
358
|
+
return record(out, root, stage, command, { status: r.status, stdout: r.stdout, stderr: r.stderr, error }, started);
|
|
359
|
+
}
|
|
360
|
+
/** Run the install run for `phase` and write its results file. Throws only on a missing phase. */
|
|
361
|
+
function runInstallRun(opts) {
|
|
362
|
+
const found = findPhaseInternal(opts.cwd, opts.phase);
|
|
363
|
+
if (!found)
|
|
364
|
+
throw new Error(`Phase ${opts.phase} not found`);
|
|
365
|
+
const phaseDirAbs = node_path_1.default.resolve(opts.cwd, found.directory);
|
|
366
|
+
const rp = getRoadmapPhaseInternal(opts.cwd, opts.phase);
|
|
367
|
+
const checks = resolveInstallChecks(rp?.section ?? null);
|
|
368
|
+
const commands = [];
|
|
369
|
+
const result = {
|
|
370
|
+
schema: exports.INSTALL_RUN_SCHEMA,
|
|
371
|
+
phase: found.phase_number,
|
|
372
|
+
phase_dir: found.directory.split(node_path_1.default.sep).join('/'),
|
|
373
|
+
generated_at: new Date().toISOString(),
|
|
374
|
+
verdict: 'fail',
|
|
375
|
+
reason: '',
|
|
376
|
+
package: { name: null, version: null, tarball: null, source: null },
|
|
377
|
+
install: { runtime: 'claude', scope: 'local', npm_mode: null, tools: null },
|
|
378
|
+
checks: { source: checks.source, line: checks.line, invalid: checks.invalid },
|
|
379
|
+
commands,
|
|
380
|
+
failures: [],
|
|
381
|
+
};
|
|
382
|
+
const root = node_path_1.default.join(node_os_1.default.tmpdir(), `pp-install-run-${node_crypto_1.default.randomUUID()}`);
|
|
383
|
+
let applicable = true;
|
|
384
|
+
let notApplicableReason = '';
|
|
385
|
+
let initDone = false;
|
|
386
|
+
let checksRun = false;
|
|
387
|
+
try {
|
|
388
|
+
node_fs_1.default.mkdirSync(root, { recursive: true });
|
|
389
|
+
const home = node_path_1.default.join(root, 'home');
|
|
390
|
+
const prefix = node_path_1.default.join(root, 'prefix');
|
|
391
|
+
const fixture = node_path_1.default.join(root, 'fixture');
|
|
392
|
+
for (const d of [home, prefix, fixture, node_path_1.default.join(root, 'pack')])
|
|
393
|
+
node_fs_1.default.mkdirSync(d, { recursive: true });
|
|
394
|
+
const env = buildRunEnv(opts.env ?? process.env, home);
|
|
395
|
+
const winShell = process.platform === 'win32';
|
|
396
|
+
// 1. The tarball: given, or built and packed from the package dir.
|
|
397
|
+
let tarballPath = null;
|
|
398
|
+
const packageDir = opts.tarball ? null : node_path_1.default.resolve(opts.cwd, opts.packageDir || '.');
|
|
399
|
+
if (opts.tarball) {
|
|
400
|
+
tarballPath = node_path_1.default.resolve(opts.cwd, opts.tarball);
|
|
401
|
+
result.package.source = 'tarball';
|
|
402
|
+
if (!node_fs_1.default.existsSync(tarballPath)) {
|
|
403
|
+
record(commands, root, 'pack', `(tarball) ${opts.tarball}`, { status: null, error: 'tarball not found' }, Date.now());
|
|
404
|
+
tarballPath = null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else if (packageDir) {
|
|
408
|
+
let pkg = null;
|
|
409
|
+
try {
|
|
410
|
+
pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(packageDir, 'package.json'), 'utf-8'));
|
|
411
|
+
}
|
|
412
|
+
catch { /* no package.json */ }
|
|
413
|
+
if (!isPpCorePackage(pkg)) {
|
|
414
|
+
applicable = false;
|
|
415
|
+
notApplicableReason = 'the package dir is not a pp-core package (package.json bin lacks pp-core and pp-tools); nothing to install';
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
result.package.source = 'pack';
|
|
419
|
+
const built = opts.build === false ? null
|
|
420
|
+
: spawnRecorded(commands, root, 'build', 'npm run build', npmCmd(), ['run', 'build'], { cwd: packageDir, env, timeout: BUILD_TIMEOUT_MS, shell: winShell });
|
|
421
|
+
if (!built || built.exit_code === 0) {
|
|
422
|
+
const packed = spawnRecorded(commands, root, 'pack', 'npm pack', npmCmd(), ['pack', '--pack-destination', node_path_1.default.join(root, 'pack')], { cwd: packageDir, env, timeout: BUILD_TIMEOUT_MS, shell: winShell });
|
|
423
|
+
const tgz = node_fs_1.default.readdirSync(node_path_1.default.join(root, 'pack')).find((f) => f.endsWith('.tgz'));
|
|
424
|
+
if (packed.exit_code === 0 && tgz)
|
|
425
|
+
tarballPath = node_path_1.default.join(root, 'pack', tgz);
|
|
426
|
+
else if (packed.exit_code === 0) {
|
|
427
|
+
packed.exit_code = 1;
|
|
428
|
+
packed.error = 'npm pack produced no .tgz';
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const harnessPath = applicable ? findSmokeHarness(packageDir) : null;
|
|
434
|
+
if (applicable && !harnessPath) {
|
|
435
|
+
applicable = false;
|
|
436
|
+
notApplicableReason = `no install harness (${HARNESS_REL.split(node_path_1.default.sep).join('/')}) in the package or the running pp-core`;
|
|
437
|
+
}
|
|
438
|
+
if (applicable && tarballPath) {
|
|
439
|
+
const meta = readTarballPackageJson(tarballPath);
|
|
440
|
+
result.package.tarball = node_path_1.default.basename(tarballPath);
|
|
441
|
+
result.package.name = meta && typeof meta['name'] === 'string' ? meta['name'] : null;
|
|
442
|
+
result.package.version = meta && typeof meta['version'] === 'string' ? meta['version'] : null;
|
|
443
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- the harness path is resolved at run time
|
|
444
|
+
const harness = require(harnessPath);
|
|
445
|
+
// 2. npm install into a temp prefix: offline first, then prefer-offline on a cache miss.
|
|
446
|
+
const installStarted = Date.now();
|
|
447
|
+
const smoke = (mode) => harness.runSmoke({
|
|
448
|
+
tarballPath, installPrefix: prefix, expectedVersion: result.package.version ?? '', lifecycleCommands: [],
|
|
449
|
+
npmEnv: { ...env, ...(mode === 'offline' ? { npm_config_offline: 'true' } : { npm_config_prefer_offline: 'true' }) },
|
|
450
|
+
});
|
|
451
|
+
let mode = 'offline';
|
|
452
|
+
let s = smoke(mode);
|
|
453
|
+
const detail = (key) => { const v = s.details[key]; return typeof v === 'string' ? v : ''; };
|
|
454
|
+
if (s.code === harness.SMOKE.INSTALL_FAILED && /ENOTCACHED/.test(detail('stderr'))) {
|
|
455
|
+
mode = 'prefer-offline';
|
|
456
|
+
s = smoke(mode);
|
|
457
|
+
}
|
|
458
|
+
result.install.npm_mode = mode;
|
|
459
|
+
const installCmd = `npm install -g --prefix <install-run>/prefix ${result.package.tarball} --${mode}`;
|
|
460
|
+
const installed = record(commands, root, 'install', installCmd, {
|
|
461
|
+
status: s.code === harness.SMOKE.OK ? 0 : 1,
|
|
462
|
+
stdout: detail('stdout'),
|
|
463
|
+
stderr: detail('stderr'),
|
|
464
|
+
error: s.code === harness.SMOKE.OK ? undefined : `harness code ${s.code}`,
|
|
465
|
+
}, installStarted);
|
|
466
|
+
// 3. The installer the user runs, in a fresh git project. The global npm
|
|
467
|
+
// prefix is the temp prefix: the installed product sees only itself
|
|
468
|
+
// installed globally, as on a fresh machine.
|
|
469
|
+
const toolEnv = { ...env, npm_config_prefix: prefix };
|
|
470
|
+
delete toolEnv.NPM_CONFIG_PREFIX;
|
|
471
|
+
if (installed.exit_code === 0) {
|
|
472
|
+
(0, node_child_process_1.spawnSync)('git', ['init', '-q'], { cwd: fixture, env, encoding: 'utf-8', timeout: COMMAND_TIMEOUT_MS, windowsHide: true });
|
|
473
|
+
const instStarted = Date.now();
|
|
474
|
+
const inst = harness.driveInstaller({
|
|
475
|
+
installPrefix: prefix, flags: ['--claude', '--local'], cwd: fixture, configRoot: node_path_1.default.join(fixture, '.claude'), env: toolEnv,
|
|
476
|
+
});
|
|
477
|
+
const tools = node_path_1.default.join(fixture, '.claude', 'pp-core', 'bin', 'pp-tools.cjs');
|
|
478
|
+
const toolsMissing = inst.ok && !node_fs_1.default.existsSync(tools);
|
|
479
|
+
record(commands, root, 'install', 'pp-core --claude --local', {
|
|
480
|
+
status: toolsMissing ? 1 : inst.status, stdout: inst.stdout, stderr: inst.stderr,
|
|
481
|
+
error: inst.reason || (toolsMissing ? 'the installer exited 0 but .claude/pp-core/bin/pp-tools.cjs is missing' : undefined),
|
|
482
|
+
}, instStarted);
|
|
483
|
+
if (inst.ok && !toolsMissing) {
|
|
484
|
+
result.install.tools = '.claude/pp-core/bin/pp-tools.cjs';
|
|
485
|
+
const runTool = (stage, label, argv) => spawnRecorded(commands, root, stage, label, process.execPath, [tools, ...argv], { cwd: fixture, env: toolEnv });
|
|
486
|
+
// 4. init, non-interactively, through the installed pp-tools.
|
|
487
|
+
for (let i = 0; i < INIT_MAX_STEPS && !initDone; i++) {
|
|
488
|
+
const next = runTool('init', 'pp-tools query init-contract --session-next --offline', ['query', 'init-contract', '--session-next', '--offline']);
|
|
489
|
+
if (next.exit_code !== 0)
|
|
490
|
+
break;
|
|
491
|
+
let state;
|
|
492
|
+
try {
|
|
493
|
+
state = JSON.parse(next.stdout);
|
|
494
|
+
}
|
|
495
|
+
catch {
|
|
496
|
+
next.exit_code = 1;
|
|
497
|
+
next.error = 'unparseable init session state';
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
if (state.done) {
|
|
501
|
+
initDone = true;
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
const step = String(state.step ?? '');
|
|
505
|
+
const answer = exports.INIT_ANSWERS[step] ?? state.preselected ?? (state.optionalAnswer ? '' : undefined);
|
|
506
|
+
if (answer === undefined) {
|
|
507
|
+
next.exit_code = 1;
|
|
508
|
+
next.error = `no fixture answer for init step "${step}"`;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
const ans = runTool('init', `pp-tools query init-contract --session-answer ${step}=… --offline`, ['query', 'init-contract', '--session-answer', `${step}=${answer}`, '--offline']);
|
|
512
|
+
if (ans.exit_code !== 0)
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
// 5. The phase's install checks against the installed copy.
|
|
516
|
+
for (const check of checks.commands)
|
|
517
|
+
runTool('check', check.text, check.argv);
|
|
518
|
+
checksRun = true;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
finally {
|
|
524
|
+
try {
|
|
525
|
+
node_fs_1.default.rmSync(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
|
|
526
|
+
}
|
|
527
|
+
catch { /* best-effort */ }
|
|
528
|
+
}
|
|
529
|
+
const v = computeInstallVerdict({ applicable, notApplicableReason, commands, invalid: checks.invalid, initDone, checksRun });
|
|
530
|
+
result.verdict = v.verdict;
|
|
531
|
+
result.reason = v.reason;
|
|
532
|
+
result.failures = v.failures;
|
|
533
|
+
const resultsFile = node_path_1.default.join(phaseDirAbs, `${found.phase_number}-${exports.INSTALL_RUN_SUFFIX}`);
|
|
534
|
+
node_fs_1.default.writeFileSync(resultsFile, JSON.stringify(result, null, 2) + '\n');
|
|
535
|
+
return { ...result, results_file: node_path_1.default.relative(opts.cwd, resultsFile).split(node_path_1.default.sep).join('/') };
|
|
536
|
+
}
|
package/pp-core/bin/lib/io.cjs
CHANGED
|
@@ -161,6 +161,7 @@ const ERROR_REASON = Object.freeze({
|
|
|
161
161
|
CONFIG_NO_FILE: 'config_no_file',
|
|
162
162
|
CONFIG_PARSE_FAILED: 'config_parse_failed',
|
|
163
163
|
CONFIG_INVALID_KEY: 'config_invalid_key',
|
|
164
|
+
CONFIG_INVALID_VALUE: 'config_invalid_value',
|
|
164
165
|
// SDK / pp-tools dispatch
|
|
165
166
|
SDK_FAIL_FAST: 'sdk_fail_fast',
|
|
166
167
|
SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
|
|
@@ -186,6 +187,20 @@ const ERROR_REASON = Object.freeze({
|
|
|
186
187
|
VAULT_PROMOTE_TARGET_REFUSED: 'vault_promote_target_refused',
|
|
187
188
|
VAULT_PROMOTE_CONCURRENT_EDIT: 'vault_promote_concurrent_edit',
|
|
188
189
|
VAULT_PROMOTE_TRANSPORT_FAILED: 'vault_promote_transport_failed',
|
|
190
|
+
// discovery rollup (discovery-v2 Phase 1: D-07/D-08 gate)
|
|
191
|
+
DISCOVERY_ROLLUP_PARSE_FAILED: 'discovery_rollup_parse_failed',
|
|
192
|
+
DISCOVERY_ROLLUP_MISMATCH: 'discovery_rollup_mismatch',
|
|
193
|
+
// discovery input package (discovery-v2 Phase 2: D-19/D-22 gate)
|
|
194
|
+
DISCOVERY_INPUT_CHECK_FAILED: 'discovery_input_check_failed',
|
|
195
|
+
// discovery research track (discovery-v2 Phase 3: D-42/D-43 gate)
|
|
196
|
+
DISCOVERY_RESEARCH_CHECK_FAILED: 'discovery_research_check_failed',
|
|
197
|
+
// discovery screen inventory (discovery-v2 Phase 4: D-52/D-53 gate)
|
|
198
|
+
DISCOVERY_SCREEN_CHECK_FAILED: 'discovery_screen_check_failed',
|
|
199
|
+
DISCOVERY_FEEDBACK_OPEN: 'discovery_feedback_open',
|
|
200
|
+
// discovery prototype element sweep
|
|
201
|
+
DISCOVERY_ELEMENT_SWEEP_FAILED: 'discovery_element_sweep_failed',
|
|
202
|
+
// discovery scope re-baselining (discovery-v2 Phase 5: D-72/D-73 gate)
|
|
203
|
+
DISCOVERY_REBASELINE_CHECK_FAILED: 'discovery_rebaseline_check_failed',
|
|
189
204
|
// generic
|
|
190
205
|
USAGE: 'usage',
|
|
191
206
|
UNKNOWN: 'unknown',
|
|
@@ -41,6 +41,8 @@ const { resolveCapabilityRuntimeState } = capabilityStateModule;
|
|
|
41
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
42
|
const capabilityActivationModule = require("./capability-activation.cjs");
|
|
43
43
|
const { _getNestedConfigValue, _readRawConfigKey, _resolveActivationValue, resolveConfigKey } = capabilityActivationModule;
|
|
44
|
+
// ─── Phase ceremony tier (pack 9): `--phase` / `--tier` overlay ───────────────
|
|
45
|
+
const phase_tier_cjs_1 = require("./phase-tier.cjs");
|
|
44
46
|
// ─── Canonical points (derived from LOOP_HOST_CONTRACT — authoritative 12) ───
|
|
45
47
|
// FIX 2: Derive the authoritative canonical set from LOOP_HOST_CONTRACT so it
|
|
46
48
|
// cannot drift from the host contract. CANONICAL_POINTS_FALLBACK is kept as an
|
|
@@ -400,6 +402,14 @@ function renderLoopHooks(resolved) {
|
|
|
400
402
|
* No JSON envelope is emitted — output is clean for shell $(…) capture.
|
|
401
403
|
* Missing <capId> value → coreError + non-zero exit.
|
|
402
404
|
* Unknown/inactive capId → `false` (not an error).
|
|
405
|
+
*
|
|
406
|
+
* --phase <N> [--tier light|standard|heavy] (pack 9): resolve the phase's
|
|
407
|
+
* ceremony tier (src/phase-tier.cts) and overlay its gate booleans
|
|
408
|
+
* (`workflow.research`, `workflow.pattern_mapper`, …) on the config snapshot
|
|
409
|
+
* BEFORE capability state and hook activation read it, so every `when` /
|
|
410
|
+
* activation key of those gates answers the tier-resolved value. Without
|
|
411
|
+
* `--phase` the project's own booleans are read as before (an explicitly
|
|
412
|
+
* invoked gate command such as /pp-secure-phase is not governed by a tier).
|
|
403
413
|
*/
|
|
404
414
|
function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
405
415
|
if (!point) {
|
|
@@ -429,6 +439,17 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
429
439
|
catch {
|
|
430
440
|
config = {};
|
|
431
441
|
}
|
|
442
|
+
const tierPhase = typeof options['phase'] === 'string' && options['phase'] !== '' ? options['phase'] : null;
|
|
443
|
+
const tierCli = typeof options['tier'] === 'string' && options['tier'] !== '' ? options['tier'] : null;
|
|
444
|
+
let phaseTier = null;
|
|
445
|
+
if (tierPhase !== null || tierCli !== null) {
|
|
446
|
+
if (tierCli !== null && !['light', 'standard', 'heavy'].includes(tierCli)) {
|
|
447
|
+
coreError('--tier needs one of light, standard, heavy');
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
phaseTier = (0, phase_tier_cjs_1.resolvePhaseTierForProject)(cwd, tierPhase, tierCli);
|
|
451
|
+
config = (0, phase_tier_cjs_1.overlayTierGates)(config, phaseTier);
|
|
452
|
+
}
|
|
432
453
|
const state = resolveCapabilityRuntimeState(cwd, runtimeConfigDir, config);
|
|
433
454
|
// Registry is the static generated module — same object capability-state uses internally.
|
|
434
455
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -458,6 +479,9 @@ function cmdLoopRenderHooks(cwd, point, raw, options = {}) {
|
|
|
458
479
|
activeHooks: resolved.activeHooks,
|
|
459
480
|
rendered,
|
|
460
481
|
};
|
|
482
|
+
if (phaseTier) {
|
|
483
|
+
envelope.phase_tier = { phase: tierPhase, tier: phaseTier.tier, source: phaseTier.source, suppressed: phaseTier.suppressed };
|
|
484
|
+
}
|
|
461
485
|
if (state.warnings && state.warnings.length > 0) {
|
|
462
486
|
envelope.warnings = state.warnings;
|
|
463
487
|
}
|