@pareto4/pp-core 1.5.4 → 1.6.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 +1 -1
- package/agents/pp-code-fixer.md +3 -3
- package/bin/install.js +0 -6
- package/commands/pp-init.md +36 -0
- package/commands/pp-ns-project.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pp-core/bin/lib/agent-install-check.cjs +1 -1
- package/pp-core/bin/lib/audit-command-router.cjs +3 -3
- package/pp-core/bin/lib/audit.cjs +1 -1
- package/pp-core/bin/lib/capability-activation.cjs +1 -1
- package/pp-core/bin/lib/capability-registry.cjs +138 -5
- package/pp-core/bin/lib/capability-state.cjs +1 -1
- package/pp-core/bin/lib/capability-writer.cjs +1 -1
- package/pp-core/bin/lib/check-command-router.cjs +190 -8
- package/pp-core/bin/lib/cjs-command-router-adapter.cjs +1 -1
- package/pp-core/bin/lib/clusters.cjs +1 -0
- package/pp-core/bin/lib/code-review-flags.cjs +1 -1
- package/pp-core/bin/lib/command-aliases.cjs +7 -0
- package/pp-core/bin/lib/config-loader.cjs +1 -1
- package/pp-core/bin/lib/config.cjs +1 -1
- package/pp-core/bin/lib/context-utilization.cjs +1 -1
- package/pp-core/bin/lib/docs.cjs +1 -1
- package/pp-core/bin/lib/frontmatter.cjs +1 -1
- package/pp-core/bin/lib/git-base-branch.cjs +2 -2
- package/pp-core/bin/lib/graphify-command-router.cjs +4 -4
- package/pp-core/bin/lib/graphify.cjs +1 -1
- package/pp-core/bin/lib/gsd2-import.cjs +1 -1
- package/pp-core/bin/lib/init-command-router.cjs +1 -1
- package/pp-core/bin/lib/init.cjs +1 -1
- package/pp-core/bin/lib/intel-command-router.cjs +5 -5
- package/pp-core/bin/lib/intel.cjs +1 -1
- package/pp-core/bin/lib/io.cjs +4 -2
- package/pp-core/bin/lib/loop-resolver.cjs +1 -1
- package/pp-core/bin/lib/phase-command-router.cjs +1 -1
- package/pp-core/bin/lib/phases-command-router.cjs +1 -1
- package/pp-core/bin/lib/plan-drift-guard.cjs +1 -1
- package/pp-core/bin/lib/platform-bindings.cjs +336 -0
- package/pp-core/bin/lib/profile-output.cjs +216 -1
- package/pp-core/bin/lib/prohibition-enforcement.cjs +2 -2
- package/pp-core/bin/lib/roadmap-command-router.cjs +2 -2
- package/pp-core/bin/lib/roadmap-parser.cjs +1 -1
- package/pp-core/bin/lib/state-command-router.cjs +1 -1
- package/pp-core/bin/lib/state.cjs +2 -2
- package/pp-core/bin/lib/validate-command-router.cjs +1 -1
- package/pp-core/bin/lib/verify-command-router.cjs +1 -1
- package/pp-core/bin/lib/verify.cjs +1 -1
- package/pp-core/bin/lib/worktree-base-ref.cjs +2 -2
- package/pp-core/bin/lib/worktree-safety.cjs +1 -1
- package/pp-core/bin/pp-tools.cjs +61 -24
- package/pp-core/workflows/discovery-sprint.md +27 -10
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +37 -0
- package/pp-core/workflows/execute-phase.md +4 -0
- package/pp-core/workflows/init.md +530 -0
- package/pp-core/workflows/new-project.md +54 -1
- package/pp-core/workflows/ship.md +24 -1
- package/pp-core/workflows/verify-work.md +1 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -0
- package/assets/gsd-logo-2000-transparent.png +0 -0
- package/assets/gsd-logo-2000-transparent.svg +0 -17
- package/assets/gsd-logo-2000.png +0 -0
- package/assets/gsd-logo-2000.svg +0 -21
|
@@ -23,6 +23,10 @@ const phaseLocatorMod = require("./phase-locator.cjs");
|
|
|
23
23
|
const { findPhaseInternal } = phaseLocatorMod;
|
|
24
24
|
const decisions_cjs_1 = require("./decisions.cjs");
|
|
25
25
|
const ui_safety_gate_cjs_1 = require("./ui-safety-gate.cjs");
|
|
26
|
+
const semver_compare_cjs_1 = require("./semver-compare.cjs");
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
|
+
const projectRootMod = require("./project-root.cjs");
|
|
29
|
+
const { findProjectRoot } = projectRootMod;
|
|
26
30
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
31
|
const verifyModule = require("./verify.cjs");
|
|
28
32
|
const { cmdVerifySchemaDrift, cmdVerifyCodebaseDrift } = verifyModule;
|
|
@@ -345,7 +349,7 @@ function cmdDecisionCoverageVerify(projectDir, args, raw) {
|
|
|
345
349
|
* Returns JSON: { frontend: boolean, hasUiSpec: boolean, block: boolean }
|
|
346
350
|
* block = frontend && !hasUiSpec (gate fires when UI work is detected but no spec exists)
|
|
347
351
|
*
|
|
348
|
-
* Invocable as:
|
|
352
|
+
* Invocable as: pp_run check ui-plan-gate <phase>
|
|
349
353
|
*
|
|
350
354
|
* Uses checkUiPresence from ui-safety-gate.cjs — does NOT reimplement frontend detection.
|
|
351
355
|
* Uses getRoadmapPhaseWithFallback + findPhaseInternal from leaf modules for phase data.
|
|
@@ -455,8 +459,8 @@ function cmdUiPlanGate(projectDir, args, raw) {
|
|
|
455
459
|
* block = frontend && hasUiFiles && !hasUiSpec
|
|
456
460
|
*
|
|
457
461
|
* Args: check ui-safety-gate <phase>
|
|
458
|
-
* Invocable as:
|
|
459
|
-
* or
|
|
462
|
+
* Invocable as: pp_run check ui-safety-gate <phase>
|
|
463
|
+
* or pp_run check ui.safety-gate <phase> (dots normalized to hyphens)
|
|
460
464
|
*
|
|
461
465
|
* Uses checkUiPresence from ui-safety-gate.cjs — does NOT reimplement frontend detection.
|
|
462
466
|
* Checks whether any files changed in recent git history match frontend file patterns.
|
|
@@ -701,7 +705,7 @@ function cmdTddReviewCheckpoint(projectDir, args, raw) {
|
|
|
701
705
|
* This gate is always advisory (passed: true) — it never blocks phase advancement.
|
|
702
706
|
*
|
|
703
707
|
* Args: check gap-analysis.plan-post <phase-dir> [phase-req-ids]
|
|
704
|
-
* Invocable as:
|
|
708
|
+
* Invocable as: pp_run check gap-analysis.plan-post <phase-dir> [phase-req-ids]
|
|
705
709
|
*/
|
|
706
710
|
function cmdGapAnalysisPlanPost(projectDir, args, raw) {
|
|
707
711
|
// args[0] = 'check', args[1] = 'gap-analysis-plan-post' (normalized), args[2] = phaseDir, args[3] = phaseReqIds
|
|
@@ -727,12 +731,182 @@ function cmdGapAnalysisPlanPost(projectDir, args, raw) {
|
|
|
727
731
|
message: result.table || result.summary || '',
|
|
728
732
|
}, raw, undefined);
|
|
729
733
|
}
|
|
734
|
+
const DRIFT_SEVERITY_ORDINAL = {
|
|
735
|
+
none: 0,
|
|
736
|
+
minor: 1,
|
|
737
|
+
major: 2,
|
|
738
|
+
breaking: 3,
|
|
739
|
+
};
|
|
740
|
+
/**
|
|
741
|
+
* Classify drift severity from pinned vs latest semver tuples.
|
|
742
|
+
*
|
|
743
|
+
* Pre-condition: compareSemverCore(pinned, latest) < 0 (pinned is strictly behind).
|
|
744
|
+
* Severity mapping (A3):
|
|
745
|
+
* differing major → 'major' (breaking aliased to major; semver can't distinguish)
|
|
746
|
+
* equal major, differing minor → 'minor'
|
|
747
|
+
* equal major+minor, differing patch → 'none' (below minor; always warn, not block)
|
|
748
|
+
*/
|
|
749
|
+
function classifyDriftSeverity(pinned, latest) {
|
|
750
|
+
const [pMaj, pMin] = (0, semver_compare_cjs_1.toNumericTuple)(pinned);
|
|
751
|
+
const [lMaj, lMin] = (0, semver_compare_cjs_1.toNumericTuple)(latest);
|
|
752
|
+
if (lMaj > pMaj)
|
|
753
|
+
return 'major';
|
|
754
|
+
if (lMin > pMin)
|
|
755
|
+
return 'minor';
|
|
756
|
+
return 'none'; // patch-level difference
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Read pareto.drift_block_on from .planning/config.json.
|
|
760
|
+
* Returns 'major' (default) when config is absent, malformed, or the key is unset.
|
|
761
|
+
*/
|
|
762
|
+
function readParetoConfig(projectDir) {
|
|
763
|
+
const configPath = node_path_1.default.join(projectDir, '.planning', 'config.json');
|
|
764
|
+
try {
|
|
765
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
766
|
+
const pareto = parsed['pareto'] ?? {};
|
|
767
|
+
const raw = pareto['drift_block_on'];
|
|
768
|
+
if (typeof raw === 'string' && raw in DRIFT_SEVERITY_ORDINAL) {
|
|
769
|
+
return { drift_block_on: raw };
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
catch { /* config absent or malformed → use default */ }
|
|
773
|
+
return { drift_block_on: 'major' };
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Resolve the latest stable tag for a platform repo via git ls-remote.
|
|
777
|
+
*
|
|
778
|
+
* T-10-04: every candidate tag is filtered through isStableTripletSemver — never shell-eval.
|
|
779
|
+
* Returns '' when offline or the remote is unreachable (callers treat this as "unknown latest").
|
|
780
|
+
*/
|
|
781
|
+
function resolveLatestTagViaGitRemote(repoShortPath) {
|
|
782
|
+
// Construct GitLab URL from "group/project" format (e.g. "pareto4/pareto-platform-gcp").
|
|
783
|
+
const remoteUrl = `https://gitlab.com/${repoShortPath}`;
|
|
784
|
+
try {
|
|
785
|
+
const lsOutput = (0, node_child_process_1.execFileSync)('git', ['ls-remote', '--tags', '--refs', '--sort=-v:refname', remoteUrl], { encoding: 'utf-8', timeout: 10_000, maxBuffer: 512 * 1024, windowsHide: true });
|
|
786
|
+
// Sort is descending by version (--sort=-v:refname) so the first isStableTripletSemver match
|
|
787
|
+
// is the latest stable tag. T-10-04: never use an unfiltered tag string.
|
|
788
|
+
const tag = lsOutput
|
|
789
|
+
.split('\n')
|
|
790
|
+
.map((line) => {
|
|
791
|
+
const m = line.match(/\brefs\/tags\/([^\s^]+)$/);
|
|
792
|
+
return m ? m[1].trim() : '';
|
|
793
|
+
})
|
|
794
|
+
.find((t) => t && (0, semver_compare_cjs_1.isStableTripletSemver)(t));
|
|
795
|
+
return tag ?? '';
|
|
796
|
+
}
|
|
797
|
+
catch {
|
|
798
|
+
return ''; // offline or unreachable → signals "unknown latest" (Pitfall 4)
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* ship.platform-drift query handler (HOOK-02).
|
|
803
|
+
*
|
|
804
|
+
* Derives a graded severity verdict from the pinned-vs-latest platform tag diff,
|
|
805
|
+
* maps it to the pareto.drift_block_on threshold, and degrades safely:
|
|
806
|
+
* - Inert when .platform-bindings.json is absent (HOOK-04 runtime file layer).
|
|
807
|
+
* - Non-blocking warn when latest is unresolved or offline (T-10-06, Pitfall 4).
|
|
808
|
+
*
|
|
809
|
+
* Verdict shape: { status: 'inert'|'pass'|'warn'|'block', block, severity, pinned, latest, present }
|
|
810
|
+
* block (boolean) mirrors status === 'block' — the uniform gate contract field the host
|
|
811
|
+
* loop reads regardless of which capability owns the gate.
|
|
812
|
+
*
|
|
813
|
+
* Invocable as: pp_run check ship.platform-drift [--latest-tag <tag>] [--raw]
|
|
814
|
+
* The dot → hyphen normalization in routeCheckCommand maps "ship.platform-drift"
|
|
815
|
+
* to the "ship-platform-drift" branch.
|
|
816
|
+
*
|
|
817
|
+
* Reuse:
|
|
818
|
+
* - Phase 7 reader contract: reads .platform-bindings.json directly at repo root
|
|
819
|
+
* (same find/parse/TOCTOU pattern as cmdValidateBindings in platform-bindings.cts).
|
|
820
|
+
* - semver-compare.cjs: toNumericTuple, compareSemverCore, isStableTripletSemver
|
|
821
|
+
* (no reimplemented semver; no shell-eval of tag strings).
|
|
822
|
+
*/
|
|
823
|
+
function cmdShipPlatformDrift(cwd, args, raw) {
|
|
824
|
+
// Step 1: Resolve project root (same approach as cmdValidateBindings in platform-bindings.cts).
|
|
825
|
+
let root;
|
|
826
|
+
try {
|
|
827
|
+
root = findProjectRoot(cwd) || cwd;
|
|
828
|
+
}
|
|
829
|
+
catch {
|
|
830
|
+
root = cwd;
|
|
831
|
+
}
|
|
832
|
+
// Step 1b: HOOK-04 runtime file layer — absent → inert (never error(), opt-in default).
|
|
833
|
+
const bindingsPath = node_path_1.default.join(root, '.platform-bindings.json');
|
|
834
|
+
if (!node_fs_1.default.existsSync(bindingsPath)) {
|
|
835
|
+
output({ status: 'inert', block: false, present: false }, raw, undefined);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
// Step 2: Parse .platform-bindings.json (malformed structure → graceful warn, not block).
|
|
839
|
+
let bindingsObj;
|
|
840
|
+
try {
|
|
841
|
+
const parsed = JSON.parse(node_fs_1.default.readFileSync(bindingsPath, 'utf-8'));
|
|
842
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
843
|
+
output({
|
|
844
|
+
status: 'warn', block: false, severity: 'none',
|
|
845
|
+
pinned: null, latest: null, present: true,
|
|
846
|
+
message: 'Malformed .platform-bindings.json structure — drift check skipped',
|
|
847
|
+
}, raw, undefined);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
bindingsObj = parsed;
|
|
851
|
+
}
|
|
852
|
+
catch {
|
|
853
|
+
// TOCTOU guard — file vanished between existsSync and readFileSync → inert path.
|
|
854
|
+
output({ status: 'inert', block: false, present: false }, raw, undefined);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
// Extract platform.tag (PINNED) and platform.repo for remote tag resolution.
|
|
858
|
+
const platformField = (typeof bindingsObj['platform'] === 'object' &&
|
|
859
|
+
bindingsObj['platform'] !== null &&
|
|
860
|
+
!Array.isArray(bindingsObj['platform'])) ? bindingsObj['platform'] : null;
|
|
861
|
+
const pinned = typeof platformField?.['tag'] === 'string' ? platformField['tag'].trim() : '';
|
|
862
|
+
const platformRepo = typeof platformField?.['repo'] === 'string' ? platformField['repo'].trim() : '';
|
|
863
|
+
// Guard: missing or invalid pinned tag → advisory warn, do not block.
|
|
864
|
+
if (!pinned || !(0, semver_compare_cjs_1.isStableTripletSemver)(pinned)) {
|
|
865
|
+
output({
|
|
866
|
+
status: 'warn', block: false, severity: 'none',
|
|
867
|
+
pinned: pinned || null, latest: null, present: true,
|
|
868
|
+
message: 'platform.tag is missing or not a valid semver in .platform-bindings.json — drift check advisory only',
|
|
869
|
+
}, raw, undefined);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
// Step 3: Resolve LATEST via --latest-tag arg (deterministic) or git ls-remote (live).
|
|
873
|
+
const latestTagArgIdx = args.indexOf('--latest-tag');
|
|
874
|
+
let latest = latestTagArgIdx >= 0 ? (args[latestTagArgIdx + 1] ?? '') : '';
|
|
875
|
+
if (!latest && platformRepo) {
|
|
876
|
+
// T-10-04: resolveLatestTagViaGitRemote filters all tags through isStableTripletSemver.
|
|
877
|
+
latest = resolveLatestTagViaGitRemote(platformRepo);
|
|
878
|
+
}
|
|
879
|
+
// Step 4: Offline / unresolved latest → non-blocking warn (T-10-06, Pitfall 4).
|
|
880
|
+
// A disconnected dev or CI run must never be blocked by an unresolvable remote.
|
|
881
|
+
if (!latest || !(0, semver_compare_cjs_1.isStableTripletSemver)(latest)) {
|
|
882
|
+
output({
|
|
883
|
+
status: 'warn', block: false, severity: 'none',
|
|
884
|
+
pinned, latest: latest || null, present: true,
|
|
885
|
+
message: 'Latest platform tag could not be resolved (offline or unreachable remote) — drift check advisory only',
|
|
886
|
+
}, raw, undefined);
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
// Step 4b: Pinned is current or ahead of latest → pass.
|
|
890
|
+
if ((0, semver_compare_cjs_1.compareSemverCore)(pinned, latest) >= 0) {
|
|
891
|
+
output({ status: 'pass', block: false, severity: 'none', pinned, latest, present: true }, raw, undefined);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
// Step 5: Classify drift severity from tuple diff (A3; breaking aliased to major for now).
|
|
895
|
+
const severity = classifyDriftSeverity(pinned, latest);
|
|
896
|
+
// Step 6: Read pareto.drift_block_on threshold (default 'major').
|
|
897
|
+
const { drift_block_on: threshold } = readParetoConfig(cwd);
|
|
898
|
+
const thresholdOrd = DRIFT_SEVERITY_ORDINAL[threshold] ?? DRIFT_SEVERITY_ORDINAL['major'];
|
|
899
|
+
const severityOrd = DRIFT_SEVERITY_ORDINAL[severity];
|
|
900
|
+
// Step 7: Emit verdict — block when severity ≥ threshold, warn otherwise.
|
|
901
|
+
const status = severityOrd >= thresholdOrd ? 'block' : 'warn';
|
|
902
|
+
output({ status, block: status === 'block', severity, pinned, latest, present: true }, raw, undefined);
|
|
903
|
+
}
|
|
730
904
|
function routeCheckCommand({ args, cwd, raw }) {
|
|
731
905
|
// Normalize dots to hyphens in the subcommand so both forms are accepted.
|
|
732
906
|
// This makes `check.query = "ui.plan-gate"` (dotted form in capability.json gates)
|
|
733
|
-
// directly runnable as `
|
|
907
|
+
// directly runnable as `pp_run check ui.plan-gate` — the dot is normalized to
|
|
734
908
|
// `ui-plan-gate` before routing. The generic gate-dispatch in §5.6 reads
|
|
735
|
-
// `check.query` from the active gate hook and runs `
|
|
909
|
+
// `check.query` from the active gate hook and runs `pp_run check ${hook.check.query}`,
|
|
736
910
|
// so the declared query must be dispatchable exactly as declared.
|
|
737
911
|
const rawSubcommand = args[1];
|
|
738
912
|
const subcommand = typeof rawSubcommand === 'string' ? rawSubcommand.replace(/\./g, '-') : rawSubcommand;
|
|
@@ -764,6 +938,13 @@ function routeCheckCommand({ args, cwd, raw }) {
|
|
|
764
938
|
cmdUiSafetyGate(cwd, args, raw);
|
|
765
939
|
return;
|
|
766
940
|
}
|
|
941
|
+
if (subcommand === 'ship-platform-drift') {
|
|
942
|
+
// ship.platform-drift — graded platform-tag drift check (HOOK-02).
|
|
943
|
+
// Dot-to-hyphen normalization means query "ship.platform-drift" routes here.
|
|
944
|
+
// Reuses Phase 7 reader contract + semver-compare; inert when bindings absent (HOOK-04).
|
|
945
|
+
cmdShipPlatformDrift(cwd, args, raw);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
767
948
|
if (subcommand === 'verify-schema-drift') {
|
|
768
949
|
// Delegates to verify.schema-drift — drift capability gate at execute:wave:post (blocking).
|
|
769
950
|
// Dot-to-hyphen normalization means query "verify.schema-drift" routes here.
|
|
@@ -783,11 +964,11 @@ function routeCheckCommand({ args, cwd, raw }) {
|
|
|
783
964
|
// The deterministic test-tier prohibition PRODUCER/gate (#1259, ADR-550 D5d). Locates the
|
|
784
965
|
// wired mechanical check (node-test or lint-rule), confirms fail-first, runs it, builds
|
|
785
966
|
// enforcementEvidence, and emits the dispositionForProhibition verdict. Invocable as
|
|
786
|
-
// `
|
|
967
|
+
// `pp_run check prohibition-enforcement <request.json>`.
|
|
787
968
|
(0, prohibition_enforcement_cjs_1.routeProhibitionEnforcement)(args, raw);
|
|
788
969
|
return;
|
|
789
970
|
}
|
|
790
|
-
error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, prohibition-enforcement, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
971
|
+
error('Unknown check subcommand. Available: auto-mode, decision-coverage-plan, decision-coverage-verify, gap-analysis-plan-post, prohibition-enforcement, ship-platform-drift, tdd-review-checkpoint, ui-plan-gate, ui-safety-gate, verify-schema-drift, verify-codebase-drift', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
791
972
|
}
|
|
792
973
|
module.exports = {
|
|
793
974
|
routeCheckCommand,
|
|
@@ -797,4 +978,5 @@ module.exports = {
|
|
|
797
978
|
computeUiSafetyGate,
|
|
798
979
|
cmdGapAnalysisPlanPost,
|
|
799
980
|
cmdTddReviewCheckpoint,
|
|
981
|
+
cmdShipPlatformDrift,
|
|
800
982
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CJS Command Router Adapter Module
|
|
4
4
|
*
|
|
5
|
-
* Compatibility routing for
|
|
5
|
+
* Compatibility routing for pp-tools.cjs command families. Uses generated
|
|
6
6
|
* command metadata for availability and small family-local argument shapers for
|
|
7
7
|
* CJS handler calls.
|
|
8
8
|
*
|
|
@@ -16,7 +16,7 @@ exports.resolveCodeReviewWorkflow = resolveCodeReviewWorkflow;
|
|
|
16
16
|
/**
|
|
17
17
|
* Parse code-review flags from an argv array. The first positional argument
|
|
18
18
|
* (phase number) is ignored — phase validation is handled by
|
|
19
|
-
* `
|
|
19
|
+
* `pp-tools query init.phase-op`. Unknown flags are silently ignored.
|
|
20
20
|
*/
|
|
21
21
|
function parseCodeReviewFlags(argv) {
|
|
22
22
|
const flags = {
|
|
@@ -583,6 +583,13 @@ exports.NON_FAMILY_COMMAND_ALIASES = [
|
|
|
583
583
|
],
|
|
584
584
|
"mutation": false
|
|
585
585
|
},
|
|
586
|
+
{
|
|
587
|
+
"canonical": "check.ship-platform-drift",
|
|
588
|
+
"aliases": [
|
|
589
|
+
"check ship-platform-drift"
|
|
590
|
+
],
|
|
591
|
+
"mutation": false
|
|
592
|
+
},
|
|
586
593
|
{
|
|
587
594
|
"canonical": "commit",
|
|
588
595
|
"aliases": [],
|
|
@@ -502,7 +502,7 @@ function loadConfig(cwd, options = {}) {
|
|
|
502
502
|
const warnKey = unknownKeys.join(',');
|
|
503
503
|
if (!_warnedUnknownConfigKeys.has(warnKey)) {
|
|
504
504
|
_warnedUnknownConfigKeys.add(warnKey);
|
|
505
|
-
process.stderr.write(`
|
|
505
|
+
process.stderr.write(`pp-tools: warning: unknown config key(s) in .planning/config.json: ${unknownKeys.join(', ')} — these will be ignored\n`);
|
|
506
506
|
}
|
|
507
507
|
}
|
|
508
508
|
// #2517 — Validate runtime/tier values
|
|
@@ -749,7 +749,7 @@ function cmdConfigPath(cwd, _raw, workstreamContext = null) {
|
|
|
749
749
|
*
|
|
750
750
|
* Wraps the Configuration Module's migrateOnDisk() for the CLI surface. This
|
|
751
751
|
* is the Phase 2 acceptance-criteria deliverable for opt-in migration (#3536):
|
|
752
|
-
* users can run `
|
|
752
|
+
* users can run `pp-tools migrate-config` to apply all four legacy-key
|
|
753
753
|
* migrations to their .planning/config.json without having to load any config
|
|
754
754
|
* implicitly via another command.
|
|
755
755
|
*
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Pure function. Callers pass tokensUsed + contextWindow; the
|
|
9
9
|
* classifier returns the percent and one of three states. Recommendation
|
|
10
10
|
* strings are NOT in this module — formatting is the renderer's job
|
|
11
|
-
* (see `validate context` in
|
|
11
|
+
* (see `validate context` in pp-tools.cjs). That separation lets the
|
|
12
12
|
* copy change without touching this module's tests.
|
|
13
13
|
*
|
|
14
14
|
* Thresholds:
|
package/pp-core/bin/lib/docs.cjs
CHANGED
|
@@ -241,7 +241,7 @@ function detectMonorepoWorkspaces(cwd) {
|
|
|
241
241
|
* resolution. Follows the cmdInitMapCodebase pattern.
|
|
242
242
|
*
|
|
243
243
|
* @example
|
|
244
|
-
* node
|
|
244
|
+
* node pp-tools.cjs docs-init --raw
|
|
245
245
|
*/
|
|
246
246
|
function cmdDocsInit(cwd, raw) {
|
|
247
247
|
const config = loadConfig(cwd);
|
|
@@ -367,7 +367,7 @@ function parseMustHavesBlock(content, blockName) {
|
|
|
367
367
|
if (items.length === 0 && blockLines.length > 0) {
|
|
368
368
|
const nonEmptyLines = blockLines.filter(l => l.trim() !== '').length;
|
|
369
369
|
if (nonEmptyLines > 0) {
|
|
370
|
-
process.stderr.write(`[
|
|
370
|
+
process.stderr.write(`[pp-tools] WARNING: must_haves.${blockName} block has ${nonEmptyLines} content lines but parsed 0 items. ` +
|
|
371
371
|
`Possible YAML formatting issue — verification will fall back to LLM-derived truths.\n`);
|
|
372
372
|
}
|
|
373
373
|
}
|
|
@@ -208,9 +208,9 @@ function gitWorktreeInfoInternal(cwd) {
|
|
|
208
208
|
}
|
|
209
209
|
// ─── CLI entry point ──────────────────────────────────────────────────────────
|
|
210
210
|
/**
|
|
211
|
-
* CLI command: `
|
|
211
|
+
* CLI command: `pp-tools git base-branch`
|
|
212
212
|
* Resolves the default branch and writes it to stdout (raw string, newline-terminated).
|
|
213
|
-
* Called by workflows via `
|
|
213
|
+
* Called by workflows via `pp_run query git.base-branch`.
|
|
214
214
|
*/
|
|
215
215
|
function cmdGitBaseBranch(cwd, _args, deps) {
|
|
216
216
|
const branch = resolveBaseBranch(cwd, deps);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* Graphify command router — CLI subcommand dispatcher for `
|
|
3
|
+
* Graphify command router — CLI subcommand dispatcher for `pp-tools graphify`.
|
|
4
4
|
*
|
|
5
5
|
* ADR-959 (phase 4d-impl-2) pilot: first real capability command cutover.
|
|
6
|
-
* Extracted from the hardcoded `case 'graphify':` arm in
|
|
6
|
+
* Extracted from the hardcoded `case 'graphify':` arm in pp-tools.cjs.
|
|
7
7
|
* Behaviour is preserved byte-for-behaviour from the prior inline case;
|
|
8
8
|
* the dispatch path now flows: default → dispatchCapabilityCommand →
|
|
9
9
|
* require(graphify-command-router.cjs) → routeGraphifyCommand.
|
|
@@ -34,7 +34,7 @@ function routeGraphifyCommand({ args, cwd, raw, error, _graphify }) {
|
|
|
34
34
|
if (subcommand === 'query') {
|
|
35
35
|
const term = args[2];
|
|
36
36
|
if (!term) {
|
|
37
|
-
error('Usage:
|
|
37
|
+
error('Usage: pp-tools graphify query <term>', ERROR_REASON.USAGE);
|
|
38
38
|
return;
|
|
39
39
|
}
|
|
40
40
|
const budgetIdx = args.indexOf('--budget');
|
|
@@ -42,7 +42,7 @@ function routeGraphifyCommand({ args, cwd, raw, error, _graphify }) {
|
|
|
42
42
|
if (budgetIdx !== -1) {
|
|
43
43
|
const rawBudget = args[budgetIdx + 1];
|
|
44
44
|
if (rawBudget === undefined || Number.isNaN(parseInt(rawBudget, 10))) {
|
|
45
|
-
error('Usage:
|
|
45
|
+
error('Usage: pp-tools graphify query <term> [--budget <N>]', ERROR_REASON.USAGE);
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
budget = parseInt(rawBudget, 10);
|
|
@@ -20,7 +20,7 @@ const { isCapabilityActive } = capabilityStateMod;
|
|
|
20
20
|
* Return the standard disabled response object.
|
|
21
21
|
*/
|
|
22
22
|
function disabledResponse() {
|
|
23
|
-
return { disabled: true, message: 'graphify is not enabled. Enable with:
|
|
23
|
+
return { disabled: true, message: 'graphify is not enabled. Enable with: pp-tools config-set graphify.enabled true' };
|
|
24
24
|
}
|
|
25
25
|
// ─── Subprocess Helper ───────────────────────────────────────────────────────
|
|
26
26
|
/**
|
|
@@ -392,7 +392,7 @@ function writePlanningDir(artifacts, planningRoot) {
|
|
|
392
392
|
}
|
|
393
393
|
// ─── Command Handler ──────────────────────────────────────────────────────────
|
|
394
394
|
/**
|
|
395
|
-
* Entry point called from
|
|
395
|
+
* Entry point called from pp-tools.cjs.
|
|
396
396
|
* Supports: --force, --dry-run, --path <dir>
|
|
397
397
|
*/
|
|
398
398
|
function cmdFromGsd2(args, cwd, raw) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Manifest-backed init subcommand router.
|
|
4
|
-
* Keeps
|
|
4
|
+
* Keeps pp-tools.cjs thin while preserving existing command semantics.
|
|
5
5
|
*
|
|
6
6
|
* Phase 6: all init.* subcommands have SDK equivalents and are dispatched
|
|
7
7
|
* via executeForCjs (the sync bridge). CJS fallback retained when:
|
package/pp-core/bin/lib/init.cjs
CHANGED
|
@@ -1218,7 +1218,7 @@ function cmdInitManager(cwd, raw) {
|
|
|
1218
1218
|
const safe = tokens.every((t) => /^--[a-zA-Z0-9][-a-zA-Z0-9]*$/.test(t) ||
|
|
1219
1219
|
/^[a-zA-Z0-9][-a-zA-Z0-9_.]*$/.test(t));
|
|
1220
1220
|
if (!safe) {
|
|
1221
|
-
process.stderr.write(`
|
|
1221
|
+
process.stderr.write(`pp-tools: warning: manager.flags contains invalid tokens, ignoring: ${val}\n`);
|
|
1222
1222
|
return '';
|
|
1223
1223
|
}
|
|
1224
1224
|
return val;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
/**
|
|
3
|
-
* Intel command router — CLI subcommand dispatcher for `
|
|
3
|
+
* Intel command router — CLI subcommand dispatcher for `pp-tools intel`.
|
|
4
4
|
*
|
|
5
5
|
* ADR-959 (phase 4d-impl-4): intel command family cutover — last first-party
|
|
6
6
|
* command cutover in the initial capability rollout.
|
|
7
|
-
* Extracted from the hardcoded `case 'intel':` arm in
|
|
7
|
+
* Extracted from the hardcoded `case 'intel':` arm in pp-tools.cjs.
|
|
8
8
|
* Behaviour is preserved byte-for-behaviour from the prior inline case;
|
|
9
9
|
* the dispatch path now flows: default → dispatchCapabilityCommand →
|
|
10
10
|
* require(intel-command-router.cjs) → routeIntelCommand.
|
|
@@ -56,7 +56,7 @@ function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
|
|
|
56
56
|
if (subcommand === 'query') {
|
|
57
57
|
const term = args[2];
|
|
58
58
|
if (!term) {
|
|
59
|
-
error('Usage:
|
|
59
|
+
error('Usage: pp-tools intel query <term>', ERROR_REASON.USAGE);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
const planningDir = path.join(cwd, '.planning');
|
|
@@ -85,7 +85,7 @@ function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
|
|
|
85
85
|
else if (subcommand === 'patch-meta') {
|
|
86
86
|
const filePath = args[2];
|
|
87
87
|
if (!filePath) {
|
|
88
|
-
error('Usage:
|
|
88
|
+
error('Usage: pp-tools intel patch-meta <file-path>', ERROR_REASON.USAGE);
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
91
|
c.output(intel.intelPatchMeta(path.resolve(cwd, filePath)), raw);
|
|
@@ -97,7 +97,7 @@ function routeIntelCommand({ args, cwd, raw, error, _intel, _core }) {
|
|
|
97
97
|
else if (subcommand === 'extract-exports') {
|
|
98
98
|
const filePath = args[2];
|
|
99
99
|
if (!filePath) {
|
|
100
|
-
error('Usage:
|
|
100
|
+
error('Usage: pp-tools intel extract-exports <file-path>', ERROR_REASON.USAGE);
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
c.output(intel.intelExtractExports(path.resolve(cwd, filePath)), raw);
|
|
@@ -240,7 +240,7 @@ function intelUpdate(planningDir) {
|
|
|
240
240
|
return disabledResponse();
|
|
241
241
|
return {
|
|
242
242
|
action: 'spawn_agent',
|
|
243
|
-
message: 'Run
|
|
243
|
+
message: 'Run pp-tools intel update or spawn gsd-intel-updater agent for full refresh',
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
246
|
/**
|
package/pp-core/bin/lib/io.cjs
CHANGED
|
@@ -161,7 +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
|
-
// SDK /
|
|
164
|
+
// SDK / pp-tools dispatch
|
|
165
165
|
SDK_FAIL_FAST: 'sdk_fail_fast',
|
|
166
166
|
SDK_UNKNOWN_COMMAND: 'sdk_unknown_command',
|
|
167
167
|
SDK_MISSING_ARG: 'sdk_missing_arg',
|
|
@@ -175,13 +175,15 @@ const ERROR_REASON = Object.freeze({
|
|
|
175
175
|
HOOKS_OPT_OUT: 'hooks_opt_out',
|
|
176
176
|
// security-scan
|
|
177
177
|
SECURITY_SCAN_FAILED: 'security_scan_failed',
|
|
178
|
+
// platform-bindings
|
|
179
|
+
PLATFORM_BINDINGS_PARSE_FAILED: 'platform_bindings_parse_failed',
|
|
178
180
|
// generic
|
|
179
181
|
USAGE: 'usage',
|
|
180
182
|
UNKNOWN: 'unknown',
|
|
181
183
|
});
|
|
182
184
|
/**
|
|
183
185
|
* Process-level flag: when true, error() emits structured JSON to stderr
|
|
184
|
-
* instead of plain "Error: <message>" text. Set by
|
|
186
|
+
* instead of plain "Error: <message>" text. Set by pp-tools.cjs when the
|
|
185
187
|
* CLI is invoked with `--json-errors`. Tests opt in to typed-IR error
|
|
186
188
|
* assertions by passing that flag and parsing the JSON.
|
|
187
189
|
*
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* at plan:pre (ui-phase) and autonomous.md at verify:post (ui-review). Further per-feature
|
|
11
11
|
* cutovers are ongoing.
|
|
12
12
|
*
|
|
13
|
-
* Command surface:
|
|
13
|
+
* Command surface: pp-tools loop render-hooks <point>
|
|
14
14
|
*
|
|
15
15
|
* Exports (three things):
|
|
16
16
|
* resolveLoopHooks({ point, registry, config }) → { point, activeHooks }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Manifest-backed phase subcommand router.
|
|
4
|
-
* Keeps
|
|
4
|
+
* Keeps pp-tools.cjs thin while preserving existing command semantics.
|
|
5
5
|
*
|
|
6
6
|
* Unsupported in this router:
|
|
7
7
|
* - scaffold: routed through top-level scaffold command.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* Manifest-backed phases subcommand router.
|
|
4
|
-
* Keeps
|
|
4
|
+
* Keeps pp-tools.cjs thin while preserving current CJS semantics.
|
|
5
5
|
*
|
|
6
6
|
* Unsupported in this router (treated as unknown):
|
|
7
7
|
* - archive: `phases archive` is excluded from the subcommands list so it
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Design constraints:
|
|
9
9
|
* - Pure module: no I/O, no require() calls, no side effects.
|
|
10
10
|
* - All inputs are validated; unknown values throw a TypeError.
|
|
11
|
-
* - Consumed by the `
|
|
11
|
+
* - Consumed by the `pp-tools drift-guard` CLI seam and by tests.
|
|
12
12
|
*
|
|
13
13
|
* Authority ladder (rung values determine MISSING severity):
|
|
14
14
|
* grep=0 intel=1 treesitter=2 lsp=3 scip=4
|