@pareto4/pp-core 1.20.0 → 1.21.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-nyquist-auditor.md +19 -14
- package/agents/pp-plan-checker.md +59 -36
- package/agents/pp-planner.md +34 -53
- package/agents/pp-verifier.md +3 -52
- package/commands/pp-autonomous.md +2 -1
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-plan-review-convergence.md +2 -1
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/pp-core/bin/lib/capability-activation.cjs +77 -0
- package/pp-core/bin/lib/capability-registry.cjs +14 -14
- package/pp-core/bin/lib/capability-state.cjs +8 -18
- package/pp-core/bin/lib/ceremony.cjs +1342 -0
- package/pp-core/bin/lib/check-command-router.cjs +62 -1
- package/pp-core/bin/lib/commands.cjs +29 -3
- package/pp-core/bin/lib/config.cjs +5 -0
- package/pp-core/bin/lib/init.cjs +50 -1
- package/pp-core/bin/lib/io.cjs +2 -0
- package/pp-core/bin/lib/loop-resolver.cjs +26 -12
- package/pp-core/bin/lib/phase.cjs +50 -8
- package/pp-core/bin/lib/plan-sizing.cjs +346 -0
- package/pp-core/bin/lib/state.cjs +1 -0
- package/pp-core/bin/lib/test-substance.cjs +3190 -0
- package/pp-core/bin/lib/timeline.cjs +605 -0
- package/pp-core/bin/pp-tools.cjs +71 -4
- package/pp-core/bin/shared/ceremony-gates.json +17 -0
- package/pp-core/bin/shared/config-schema.manifest.json +1 -0
- package/pp-core/references/ceremony.md +81 -0
- package/pp-core/references/few-shot-examples/plan-checker.md +8 -8
- package/pp-core/references/phase-timeline.md +107 -0
- package/pp-core/references/planner-antipatterns.md +32 -0
- package/pp-core/references/planner-guidance.md +17 -14
- package/pp-core/references/planner-revision.md +1 -1
- package/pp-core/references/planning-config.md +3 -2
- package/pp-core/references/test-substance.md +324 -0
- package/pp-core/references/verifier-stub-patterns.md +51 -0
- package/pp-core/templates/VALIDATION.md +1 -0
- package/pp-core/templates/phase-prompt.md +19 -7
- package/pp-core/templates/roadmap.md +1 -1
- package/pp-core/templates/verification-report.md +1 -1
- package/pp-core/workflows/audit-milestone.md +4 -1
- package/pp-core/workflows/autonomous.md +11 -8
- package/pp-core/workflows/code-review.md +21 -9
- package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -7
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +3 -2
- package/pp-core/workflows/execute-phase.md +15 -4
- package/pp-core/workflows/init/steps/client-context-capture.md +1 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +7 -3
- package/pp-core/workflows/plan-phase/steps/prd-express.md +104 -0
- package/pp-core/workflows/plan-phase.md +47 -114
- package/pp-core/workflows/plan-review-convergence.md +6 -3
- package/pp-core/workflows/profile-user.md +2 -2
- package/pp-core/workflows/quick.md +1 -1
- package/pp-core/workflows/ship.md +3 -3
- package/pp-core/workflows/spec-phase.md +2 -2
- package/pp-core/workflows/stats.md +19 -4
- package/pp-core/workflows/validate-phase.md +19 -4
- package/pp-core/workflows/verify-phase.md +1 -1
- package/pp-core/workflows/verify-work.md +1 -1
- package/scripts/gen-capability-registry.cjs +43 -45
- package/scripts/run-tests.cjs +54 -30
|
@@ -34,6 +34,12 @@ const SCHEMA_VERSION = '1';
|
|
|
34
34
|
// registry generator and the loop-host-contract generator share one source of truth.
|
|
35
35
|
const { LOOP_HOST_CONTRACT } = require('../pp-core/bin/lib/loop-host-contract.cjs');
|
|
36
36
|
|
|
37
|
+
// Ceremony gate vocabulary (Phase 37, D-14) — a hook `when` conjunct `ceremony.<gate>`
|
|
38
|
+
// must name a gate declared here.
|
|
39
|
+
const CEREMONY_GATE_NAMES = new Set(
|
|
40
|
+
require('../pp-core/bin/shared/ceremony-gates.json').gates.map((g) => g.key),
|
|
41
|
+
);
|
|
42
|
+
|
|
37
43
|
// Wired-points helper — tells us which points actually have render-hooks call sites.
|
|
38
44
|
const { getWiredLoopPoints } = require('./gen-loop-host-contract.cjs');
|
|
39
45
|
|
|
@@ -1241,6 +1247,38 @@ function validateGate(gate, prefix) {
|
|
|
1241
1247
|
|
|
1242
1248
|
// ─── Contract validation ──────────────────────────────────────────────────────
|
|
1243
1249
|
|
|
1250
|
+
/**
|
|
1251
|
+
* Validate a hook `when` (Phase 37, D-14). A `when` is one or more conjuncts joined
|
|
1252
|
+
* by `&&`; each is a config key the capability declares, or `ceremony.<gate>` with
|
|
1253
|
+
* the gate named in pp-core/bin/shared/ceremony-gates.json. A non-string `when` is
|
|
1254
|
+
* already reported by the schema checks and is skipped here. Pushes onto `errors`.
|
|
1255
|
+
* `where` is the message prefix, e.g. `capability "x" step.when`, so the message of
|
|
1256
|
+
* a single undeclared key stays `<where> "<key>" is not defined in capability config keys`.
|
|
1257
|
+
*/
|
|
1258
|
+
function validateWhen(when, cap, where, errors) {
|
|
1259
|
+
if (typeof when !== 'string') return;
|
|
1260
|
+
for (const raw of when.split('&&')) {
|
|
1261
|
+
const conjunct = raw.trim();
|
|
1262
|
+
if (conjunct === '') {
|
|
1263
|
+
errors.push(where + ' has an empty conjunct');
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (conjunct.startsWith('ceremony.')) {
|
|
1267
|
+
if (!CEREMONY_GATE_NAMES.has(conjunct.slice('ceremony.'.length))) {
|
|
1268
|
+
errors.push(where + ' "' + conjunct + '" is not a ceremony gate');
|
|
1269
|
+
}
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
if (
|
|
1273
|
+
typeof cap.config === 'object' &&
|
|
1274
|
+
cap.config !== null &&
|
|
1275
|
+
!Object.prototype.hasOwnProperty.call(cap.config, conjunct)
|
|
1276
|
+
) {
|
|
1277
|
+
errors.push(where + ' "' + conjunct + '" is not defined in capability config keys');
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1244
1282
|
/**
|
|
1245
1283
|
* Validate per-capability contract constraints against the Loop Host Contract.
|
|
1246
1284
|
* This covers:
|
|
@@ -1281,51 +1319,10 @@ function validateAgainstContract(cap, capId, knownAgentIds) {
|
|
|
1281
1319
|
}
|
|
1282
1320
|
}
|
|
1283
1321
|
|
|
1284
|
-
// when
|
|
1285
|
-
for (const step of cap.steps)
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
if (
|
|
1289
|
-
typeof cap.config === 'object' &&
|
|
1290
|
-
cap.config !== null &&
|
|
1291
|
-
!Object.prototype.hasOwnProperty.call(cap.config, step.when)
|
|
1292
|
-
) {
|
|
1293
|
-
errors.push(
|
|
1294
|
-
prefix + ' step.when "' + step.when + '" is not defined in capability config keys',
|
|
1295
|
-
);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
for (const contrib of cap.contributions) {
|
|
1301
|
-
if (contrib.when !== undefined) {
|
|
1302
|
-
if (typeof contrib.when !== 'string') continue;
|
|
1303
|
-
if (
|
|
1304
|
-
typeof cap.config === 'object' &&
|
|
1305
|
-
cap.config !== null &&
|
|
1306
|
-
!Object.prototype.hasOwnProperty.call(cap.config, contrib.when)
|
|
1307
|
-
) {
|
|
1308
|
-
errors.push(
|
|
1309
|
-
prefix + ' contribution.when "' + contrib.when + '" is not defined in capability config keys',
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
for (const gate of cap.gates) {
|
|
1316
|
-
if (gate.when !== undefined) {
|
|
1317
|
-
if (typeof gate.when !== 'string') continue;
|
|
1318
|
-
if (
|
|
1319
|
-
typeof cap.config === 'object' &&
|
|
1320
|
-
cap.config !== null &&
|
|
1321
|
-
!Object.prototype.hasOwnProperty.call(cap.config, gate.when)
|
|
1322
|
-
) {
|
|
1323
|
-
errors.push(
|
|
1324
|
-
prefix + ' gate.when "' + gate.when + '" is not defined in capability config keys',
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1322
|
+
// when: one or more `&&` conjuncts, each a declared config key or `ceremony.<gate>`
|
|
1323
|
+
for (const step of cap.steps) validateWhen(step.when, cap, prefix + ' step.when', errors);
|
|
1324
|
+
for (const contrib of cap.contributions) validateWhen(contrib.when, cap, prefix + ' contribution.when', errors);
|
|
1325
|
+
for (const gate of cap.gates) validateWhen(gate.when, cap, prefix + ' gate.when', errors);
|
|
1329
1326
|
|
|
1330
1327
|
return errors;
|
|
1331
1328
|
}
|
|
@@ -2759,6 +2756,7 @@ function main() {
|
|
|
2759
2756
|
module.exports = {
|
|
2760
2757
|
validateCapability,
|
|
2761
2758
|
validateAgainstContract,
|
|
2759
|
+
validateWhen,
|
|
2762
2760
|
validateConsumesGlobal,
|
|
2763
2761
|
validateCrossCapability,
|
|
2764
2762
|
validateSealField,
|
package/scripts/run-tests.cjs
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
const { readdirSync } = require('fs');
|
|
33
33
|
const { join, basename } = require('path');
|
|
34
|
-
const { execFileSync } = require('child_process');
|
|
34
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
35
35
|
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
36
36
|
|
|
37
37
|
const SUITES = ['all', 'unit', 'integration', 'install', 'security', 'slow'];
|
|
@@ -548,42 +548,66 @@ function main() {
|
|
|
548
548
|
if (chunks.length > 1) {
|
|
549
549
|
console.error(`run-tests: chunk ${i + 1}/${chunks.length} — ${chunks[i].length} files`);
|
|
550
550
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
551
|
+
const chunkFiles = chunks[i].map(f => f.split(/[\\/]/).pop()).join(' ');
|
|
552
|
+
// stdout is captured, not inherited, so the chunk's node:test summary can be
|
|
553
|
+
// checked; it is written through unchanged once the chunk ends. stderr stays
|
|
554
|
+
// live. A chunk can exit 0 with its output cut off and no summary — on CI
|
|
555
|
+
// (2026-10-28) two of five chunks did, and ~1,000 tests never reported while
|
|
556
|
+
// the job stayed green — so a missing summary fails the run.
|
|
557
|
+
const result = spawnSync(
|
|
558
|
+
process.execPath,
|
|
559
|
+
['--test', ...(forceExit ? ['--test-force-exit'] : []), concurrency, ...chunks[i]],
|
|
560
|
+
{
|
|
561
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
562
|
+
env: { ...process.env },
|
|
563
|
+
timeout: chunkTimeoutMs,
|
|
564
|
+
maxBuffer: 1024 * 1024 * 1024,
|
|
565
|
+
},
|
|
566
|
+
);
|
|
567
|
+
if (result.stdout && result.stdout.length > 0) process.stdout.write(result.stdout);
|
|
568
|
+
// When the per-chunk timeout fires, spawnSync kills the child and reports
|
|
569
|
+
// error.code === 'ETIMEDOUT' (POSIX) and/or a kill signal (platform-dependent).
|
|
570
|
+
// Check both so detection holds on Windows and POSIX.
|
|
571
|
+
const timedOut = (result.error && result.error.code === 'ETIMEDOUT') ||
|
|
572
|
+
(result.signal !== null && result.status === null && !result.error);
|
|
573
|
+
let code = result.status === null ? 1 : result.status;
|
|
574
|
+
if (result.error && result.error.code === 'ETIMEDOUT') code = 1;
|
|
575
|
+
if (timedOut) {
|
|
576
|
+
console.error(
|
|
577
|
+
`run-tests: chunk ${i + 1}/${chunks.length} exceeded the per-chunk timeout ` +
|
|
578
|
+
`of ${chunkTimeoutMs}ms and was killed — a test in this chunk is likely leaking ` +
|
|
579
|
+
`an open handle (un-terminated Worker, un-killed child process, or ref'd timer) ` +
|
|
580
|
+
`so node --test never exits. Files: ${chunkFiles}`,
|
|
560
581
|
);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
`
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
`so node --test never exits. Files: ${chunks[i]
|
|
572
|
-
.map(f => f.split(/[\\/]/).pop())
|
|
573
|
-
.join(' ')}`,
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
const code = err.status || 1;
|
|
577
|
-
// Run every chunk so the operator sees all failures in one pass; report
|
|
578
|
-
// the first non-zero exit at the end.
|
|
579
|
-
if (firstFailureExit === 0) firstFailureExit = code;
|
|
582
|
+
} else if (result.error) {
|
|
583
|
+
console.error(`run-tests: chunk ${i + 1}/${chunks.length} could not run: ${result.error.message}`);
|
|
584
|
+
code = 1;
|
|
585
|
+
} else if (code === 0 && !hasTestSummary(result.stdout)) {
|
|
586
|
+
console.error(
|
|
587
|
+
`run-tests: chunk ${i + 1}/${chunks.length} exited without a test summary ` +
|
|
588
|
+
`(no "ℹ tests N" / "ℹ fail N" lines) — its output was cut off, so some of its ` +
|
|
589
|
+
`tests never reported. Files: ${chunkFiles}`,
|
|
590
|
+
);
|
|
591
|
+
code = 1;
|
|
580
592
|
}
|
|
593
|
+
// Run every chunk so the operator sees all failures in one pass; report
|
|
594
|
+
// the first non-zero exit at the end.
|
|
595
|
+
if (code !== 0 && firstFailureExit === 0) firstFailureExit = code;
|
|
581
596
|
}
|
|
582
597
|
if (firstFailureExit !== 0) return firstFailureExit;
|
|
583
598
|
}
|
|
584
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Whether a chunk's stdout carries node:test's closing summary: both the
|
|
602
|
+
* `tests N` and `fail N` lines, from the spec reporter (`ℹ`) or TAP (`#`).
|
|
603
|
+
*/
|
|
604
|
+
function hasTestSummary(stdout) {
|
|
605
|
+
const text = stdout ? stdout.toString('utf8') : '';
|
|
606
|
+
return /^(?:ℹ|#) tests \d+\s*$/m.test(text) && /^(?:ℹ|#) fail \d+\s*$/m.test(text);
|
|
607
|
+
}
|
|
608
|
+
|
|
585
609
|
if (require.main === module) {
|
|
586
610
|
runMain(main);
|
|
587
611
|
}
|
|
588
612
|
|
|
589
|
-
module.exports = { suiteOf, ensureBuiltArtifacts, parseShardArg, selectShard };
|
|
613
|
+
module.exports = { suiteOf, ensureBuiltArtifacts, parseShardArg, selectShard, hasTestSummary };
|