@pareto4/pp-core 1.8.2 → 1.10.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.
Files changed (55) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +3 -0
  3. package/agents/pp-code-fixer.md +11 -0
  4. package/agents/pp-code-reviewer.md +77 -1
  5. package/agents/pp-executor.md +34 -1
  6. package/agents/pp-planner.md +24 -103
  7. package/bin/install.js +9 -1
  8. package/commands/pp-ns-manage.md +2 -1
  9. package/commands/pp-vault-promote.md +31 -0
  10. package/gemini-extension.json +1 -1
  11. package/package.json +6 -3
  12. package/pareto-skills/pp-project-setup/SKILL.md +32 -440
  13. package/pp-core/bin/lib/capability-registry.cjs +111 -2
  14. package/pp-core/bin/lib/capability-state.cjs +54 -10
  15. package/pp-core/bin/lib/check-command-router.cjs +473 -3
  16. package/pp-core/bin/lib/clusters.cjs +3 -0
  17. package/pp-core/bin/lib/config.cjs +7 -22
  18. package/pp-core/bin/lib/io.cjs +7 -0
  19. package/pp-core/bin/lib/loop-resolver.cjs +6 -0
  20. package/pp-core/bin/lib/practices-review-check.cjs +938 -0
  21. package/pp-core/bin/lib/profile-output.cjs +4 -2
  22. package/pp-core/bin/lib/vault-command-router.cjs +108 -2
  23. package/pp-core/bin/lib/vault-practices.cjs +982 -0
  24. package/pp-core/bin/lib/vault-write-back.cjs +756 -0
  25. package/pp-core/bin/lib/vault.cjs +45 -3
  26. package/pp-core/bin/pp-tools.cjs +26 -0
  27. package/pp-core/bin/shared/config-schema.manifest.json +4 -0
  28. package/pp-core/references/planner-checkpoints.md +72 -0
  29. package/pp-core/references/planner-tdd.md +55 -0
  30. package/pp-core/references/planner-vault-practices.md +67 -0
  31. package/pp-core/templates/practices/README.md +122 -0
  32. package/pp-core/templates/practices/error-handling.md +18 -0
  33. package/pp-core/templates/practices/minimal-comments.md +14 -0
  34. package/pp-core/templates/practices/naming-convention.md +18 -0
  35. package/pp-core/templates/practices/practice-template.md +23 -0
  36. package/pp-core/workflows/code-review.md +74 -18
  37. package/pp-core/workflows/discovery-sprint.md +409 -16
  38. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +250 -0
  39. package/pp-core/workflows/discovery-to-build.md +218 -48
  40. package/pp-core/workflows/discuss-phase.md +1 -1
  41. package/pp-core/workflows/execute-phase.md +1 -1
  42. package/pp-core/workflows/plan-phase.md +1 -1
  43. package/pp-core/workflows/ship.md +1 -0
  44. package/pp-core/workflows/vault-write-back.md +213 -0
  45. package/scripts/changeset/lint.cjs +20 -5
  46. package/scripts/gen-capability-registry.cjs +68 -8
  47. package/scripts/lint-allow-test-rule-refs.allowlist.json +8 -1
  48. package/scripts/lint-descriptions.cjs +3 -1
  49. package/scripts/lint-docs-required.cjs +10 -3
  50. package/scripts/lint-legacy-dir-name.cjs +5 -0
  51. package/scripts/lint-test-file-count.allowlist.json +10 -1
  52. package/scripts/release-channel.cjs +58 -0
  53. package/scripts/release-tarball-smoke.cjs +4 -0
  54. package/scripts/verify-opengsd-sweep.cjs +12 -0
  55. package/scripts/verify-rename-guard.cjs +333 -0
@@ -441,8 +441,10 @@ function generateSkillsSection(cwd) {
441
441
  for (const entry of entries) {
442
442
  if (!entry.isDirectory())
443
443
  continue;
444
- // Skip GSD's own installed skills — only surface project-specific skills
445
- if (entry.name.startsWith('gsd-'))
444
+ // Skip pp-core's own installed skills (current 'pp-' naming plus legacy
445
+ // 'gsd-' directories, backward compatibility retained) — only surface
446
+ // project-specific skills.
447
+ if (entry.name.startsWith('pp-') || entry.name.startsWith('gsd-'))
446
448
  continue;
447
449
  const skillMdPath = node_path_1.default.join(absDir, entry.name, 'SKILL.md');
448
450
  if (!node_fs_1.default.existsSync(skillMdPath))
@@ -29,11 +29,12 @@
29
29
  */
30
30
  // eslint-disable-next-line @typescript-eslint/no-require-imports
31
31
  const io = require("./io.cjs");
32
+ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
32
33
  const { ERROR_REASON } = io;
33
34
  // Default CoreModule implementation. _core seam overrides this for test injection.
34
35
  const _defaultCore = { output: io.output };
35
36
  // ─── Implementation ───────────────────────────────────────────────────────────
36
- function routeVaultCommand({ args, cwd, raw, error, _vault, _core }) {
37
+ function routeVaultCommand({ args, cwd, raw, error, _vault, _core, _practices, _exec, _writeBack }) {
37
38
  // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
38
39
  const vault = _vault ?? require('./vault.cjs');
39
40
  const c = _core ?? _defaultCore;
@@ -47,8 +48,113 @@ function routeVaultCommand({ args, cwd, raw, error, _vault, _core }) {
47
48
  else if (subcommand === 'summary') {
48
49
  c.output(vault.vaultSummary(cwd), raw);
49
50
  }
51
+ else if (subcommand === 'practices') {
52
+ const verb = args[2];
53
+ if (verb === 'match') {
54
+ // T-24-08b: reject any unrecognized `--`-prefixed argument BEFORE the
55
+ // `--changed` git call (so a typo never spawns a subprocess) instead
56
+ // of silently filtering it out of the positional file list. Matched on
57
+ // the WHOLE token so `--files=…` is caught by the same check as
58
+ // `--files value`. DECISION (recorded, not deferred): `--files=` is
59
+ // NOT implemented as a comma-splitting alias here, unlike
60
+ // `/pp-code-review`'s `--files=a,b` (pp-core/workflows/code-review.md).
61
+ // The auditor marked the alias "optional and secondary"; the required
62
+ // behavior is loud failure, and the error message below names the
63
+ // supported forms instead. The host (pp-core/bin/pp-tools.cjs) already
64
+ // splices out its own global flags (--json-errors/--cwd/--ws/--raw/
65
+ // --pick/--default) before dispatch, which is why the recognized set
66
+ // here is only two flags wide.
67
+ const RECOGNIZED_MATCH_FLAGS = new Set(['--changed', '--raw', '--block']);
68
+ const unrecognizedFlag = args.slice(3).find((a) => a.startsWith('--') && !RECOGNIZED_MATCH_FLAGS.has(a));
69
+ if (unrecognizedFlag !== undefined) {
70
+ error(`Unrecognized flag "${unrecognizedFlag}" for vault practices match. Supported: positional file paths, --changed, --raw, or --block.`, ERROR_REASON.SDK_FAIL_FAST);
71
+ return;
72
+ }
73
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
74
+ const practices = _practices ?? require('./vault-practices.cjs');
75
+ const runGit = _exec ?? shell_command_projection_cjs_1.execGit;
76
+ let files;
77
+ if (args.includes('--changed')) {
78
+ const gitResult = runGit(['diff', '--name-only', 'HEAD'], { cwd });
79
+ if (gitResult.exitCode !== 0) {
80
+ error('Could not determine changed files: git diff failed (WR-02)', ERROR_REASON.SDK_FAIL_FAST);
81
+ return;
82
+ }
83
+ files = gitResult.stdout.split('\n').map((f) => f.trim()).filter((f) => f.length > 0);
84
+ }
85
+ else {
86
+ files = args.slice(3).filter((a) => !a.startsWith('--'));
87
+ }
88
+ const result = practices.matchPractices({ cwd, files });
89
+ if (args.includes('--block')) {
90
+ // T-25-08: emit the finished <vault_practices> block so the planner
91
+ // embeds it VERBATIM. The planner never renames a field or applies a
92
+ // fence itself — that transformation used to be prose-only, which is
93
+ // what left the phase's prompt-injection mitigation unenforced.
94
+ c.output(result, true, practices.renderPracticesBlock(result));
95
+ }
96
+ else if (raw) {
97
+ c.output(result, true, JSON.stringify(result));
98
+ }
99
+ else {
100
+ c.output(result, true, practices.renderPracticesReport(result));
101
+ }
102
+ }
103
+ else if (verb === 'promote') {
104
+ // T-29-16: reject any unrecognized `--`-prefixed argument BEFORE the
105
+ // lazy require of the write-back module and before any subprocess can
106
+ // spawn — mirrors the `match` branch's T-24-08b guard exactly. Matched
107
+ // on the WHOLE token so `--name=value` is caught by the same check as
108
+ // `--name value`. The recognized set holds exactly three flags — no
109
+ // flag that disables, bypasses, or relaxes any guard exists.
110
+ const RECOGNIZED_PROMOTE_FLAGS = new Set(['--confirmed', '--expect', '--raw']);
111
+ const promoteArgs = args.slice(3);
112
+ const unrecognizedPromoteFlag = promoteArgs.find((a) => a.startsWith('--') && !RECOGNIZED_PROMOTE_FLAGS.has(a));
113
+ if (unrecognizedPromoteFlag !== undefined) {
114
+ error(`Unrecognized flag "${unrecognizedPromoteFlag}" for vault practices promote. Supported: --confirmed, --expect <token>, --raw.`, ERROR_REASON.SDK_FAIL_FAST);
115
+ return;
116
+ }
117
+ // Positional file path: the first token that is not `--`-prefixed and
118
+ // is not the value immediately following `--expect`.
119
+ const expectFlagIdx = promoteArgs.indexOf('--expect');
120
+ const expectValueIdx = expectFlagIdx !== -1 ? expectFlagIdx + 1 : -1;
121
+ const filePath = promoteArgs.find((a, i) => !a.startsWith('--') && i !== expectValueIdx);
122
+ if (filePath === undefined) {
123
+ error('No practice file was given. Usage: vault practices promote <file> [--confirmed --expect <token>] [--raw].', ERROR_REASON.SDK_MISSING_ARG);
124
+ return;
125
+ }
126
+ const confirmed = promoteArgs.includes('--confirmed');
127
+ const expect = expectValueIdx !== -1 ? promoteArgs[expectValueIdx] : undefined;
128
+ if (confirmed && (expect === undefined || expect.startsWith('--'))) {
129
+ error('This practice was not pushed: the confirmation is missing the preview token it needs. ' +
130
+ 'Run the promotion without confirming first, then pass the token it prints.', ERROR_REASON.VAULT_PROMOTE_CONCURRENT_EDIT);
131
+ return;
132
+ }
133
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
134
+ const writeBack = _writeBack ?? require('./vault-write-back.cjs');
135
+ const verdict = writeBack.promotePractice({ cwd, filePath, confirmed, expect });
136
+ // Emit the structured verdict BEFORE any error call — a refusal still
137
+ // carries its verdict to stdout. This ordering is the whole point of
138
+ // the uniform D-16 refusal contract and is what lets a wrapper
139
+ // workflow render results without scraping prose.
140
+ if (raw) {
141
+ c.output(verdict, true, JSON.stringify(verdict));
142
+ }
143
+ else {
144
+ c.output(verdict, true, writeBack.renderPromoteReport(verdict));
145
+ }
146
+ if (verdict.status === 'refused') {
147
+ // An action command's refusal must be observable in the exit code —
148
+ // do not adopt the ship gate's always-exit-zero convention here.
149
+ error(verdict.message ?? 'This practice was not promoted.', verdict.reason ?? ERROR_REASON.UNKNOWN);
150
+ }
151
+ }
152
+ else {
153
+ error('Unknown practices subcommand. Available: match, promote', ERROR_REASON.SDK_UNKNOWN_COMMAND);
154
+ }
155
+ }
50
156
  else {
51
- error('Unknown vault subcommand. Available: fetch, status, summary', ERROR_REASON.SDK_UNKNOWN_COMMAND);
157
+ error('Unknown vault subcommand. Available: fetch, status, summary, practices', ERROR_REASON.SDK_UNKNOWN_COMMAND);
52
158
  }
53
159
  }
54
160
  module.exports = {