@pareto4/pp-core 1.18.0 → 1.20.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 (194) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/pp-code-fixer.md +9 -5
  3. package/agents/pp-code-reviewer.md +1 -0
  4. package/agents/pp-codebase-mapper.md +3 -0
  5. package/agents/pp-debugger.md +3 -2
  6. package/agents/pp-executor.md +16 -7
  7. package/agents/pp-integration-checker.md +1 -0
  8. package/agents/pp-phase-researcher.md +13 -1
  9. package/agents/pp-plan-checker.md +3 -0
  10. package/agents/pp-planner.md +20 -1
  11. package/agents/pp-research-synthesizer.md +1 -1
  12. package/agents/pp-ui-auditor.md +1 -0
  13. package/agents/pp-ui-researcher.md +3 -1
  14. package/agents/pp-vault-reviewer.md +2 -0
  15. package/agents/pp-verifier.md +22 -10
  16. package/commands/pp-add-tests.md +1 -1
  17. package/commands/pp-debug.md +1 -0
  18. package/commands/pp-discovery.md +3 -2
  19. package/commands/pp-init.md +4 -3
  20. package/commands/pp-quick.md +3 -0
  21. package/commands/pp-review-backlog.md +2 -1
  22. package/gemini-extension.json +1 -1
  23. package/package.json +6 -3
  24. package/pareto-skills/_shared/pp-claude-md-protocol.md +1 -1
  25. package/pareto-skills/pp-project-setup/SKILL.md +18 -343
  26. package/pp-core/bin/lib/capability-registry.cjs +3 -3
  27. package/pp-core/bin/lib/check-command-router.cjs +60 -20
  28. package/pp-core/bin/lib/commands.cjs +79 -4
  29. package/pp-core/bin/lib/compose-bindings.cjs +97 -14
  30. package/pp-core/bin/lib/discovery-access.cjs +276 -0
  31. package/pp-core/bin/lib/discovery-charter.cjs +664 -0
  32. package/pp-core/bin/lib/discovery-deviations.cjs +264 -0
  33. package/pp-core/bin/lib/discovery-entities.cjs +323 -0
  34. package/pp-core/bin/lib/discovery-for-phase.cjs +628 -0
  35. package/pp-core/bin/lib/discovery-integrations.cjs +286 -0
  36. package/pp-core/bin/lib/discovery-migration.cjs +246 -0
  37. package/pp-core/bin/lib/discovery-phases.cjs +233 -0
  38. package/pp-core/bin/lib/discovery-processes.cjs +355 -0
  39. package/pp-core/bin/lib/discovery-research-track.cjs +5 -4
  40. package/pp-core/bin/lib/discovery-rollup.cjs +12 -8
  41. package/pp-core/bin/lib/discovery-rules.cjs +216 -0
  42. package/pp-core/bin/lib/discovery-screens.cjs +21 -3
  43. package/pp-core/bin/lib/discovery-session-check.cjs +264 -0
  44. package/pp-core/bin/lib/discovery-stories.cjs +273 -0
  45. package/pp-core/bin/lib/discovery-sweep.cjs +13 -4
  46. package/pp-core/bin/lib/discovery-table.cjs +55 -0
  47. package/pp-core/bin/lib/discovery-vault-check.cjs +37 -3
  48. package/pp-core/bin/lib/init-contract.cjs +48 -7
  49. package/pp-core/bin/lib/io.cjs +22 -0
  50. package/pp-core/bin/lib/vault-coordinates.cjs +1 -1
  51. package/pp-core/bin/lib/vault-index.cjs +18 -5
  52. package/pp-core/bin/lib/vault-standards-review.cjs +50 -2
  53. package/pp-core/bin/lib/vault-update.cjs +4 -0
  54. package/pp-core/bin/lib/vault.cjs +2 -2
  55. package/pp-core/bin/pp-tools.cjs +153 -5
  56. package/pp-core/org-profile.json +1 -1
  57. package/pp-core/references/autonomous-best-effort.md +3 -2
  58. package/pp-core/references/autonomous-lifecycle.md +1 -0
  59. package/pp-core/references/autonomous-smart-discuss.md +5 -1
  60. package/pp-core/references/discovery-access.md +112 -0
  61. package/pp-core/references/discovery-build-consumption.md +256 -0
  62. package/pp-core/references/discovery-charter.md +127 -0
  63. package/pp-core/references/discovery-data-model.md +145 -0
  64. package/pp-core/references/discovery-id-conventions.md +77 -8
  65. package/pp-core/references/discovery-integrations.md +151 -0
  66. package/pp-core/references/discovery-processes.md +190 -0
  67. package/pp-core/references/discovery-rebaseline.md +2 -2
  68. package/pp-core/references/discovery-research-track.md +5 -6
  69. package/pp-core/references/discovery-session-output.md +70 -0
  70. package/pp-core/references/discovery-standards.md +107 -0
  71. package/pp-core/references/discovery-traceability.md +2 -2
  72. package/pp-core/references/execute-mvp-tdd.md +3 -3
  73. package/pp-core/references/git-integration.md +57 -33
  74. package/pp-core/references/git-planning-commit.md +31 -9
  75. package/pp-core/references/phase-argument-parsing.md +1 -0
  76. package/pp-core/references/planner-discovery-extract.md +35 -0
  77. package/pp-core/references/planner-gap-closure.md +1 -0
  78. package/pp-core/references/planner-revision.md +3 -1
  79. package/pp-core/references/planner-tdd.md +4 -4
  80. package/pp-core/references/planning-config.md +3 -3
  81. package/pp-core/references/shell-portability.md +28 -0
  82. package/pp-core/references/tdd.md +20 -16
  83. package/pp-core/references/verifier-discovery-conformance.md +36 -0
  84. package/pp-core/references/worktree-path-safety.md +1 -0
  85. package/pp-core/templates/milestone.md +5 -5
  86. package/pp-core/templates/project-charter.md +161 -100
  87. package/pp-core/templates/project.md +3 -3
  88. package/pp-core/templates/requirements.md +18 -0
  89. package/pp-core/templates/verification-report.md +16 -0
  90. package/pp-core/workflows/add-backlog.md +1 -1
  91. package/pp-core/workflows/add-tests.md +5 -2
  92. package/pp-core/workflows/add-todo.md +3 -1
  93. package/pp-core/workflows/ai-integration-phase.md +2 -1
  94. package/pp-core/workflows/audit-fix.md +4 -3
  95. package/pp-core/workflows/audit-milestone.md +1 -0
  96. package/pp-core/workflows/autonomous.md +6 -0
  97. package/pp-core/workflows/check-todos.md +2 -1
  98. package/pp-core/workflows/cleanup.md +3 -1
  99. package/pp-core/workflows/code-review-fix.md +12 -5
  100. package/pp-core/workflows/code-review.md +15 -1
  101. package/pp-core/workflows/complete-milestone.md +13 -3
  102. package/pp-core/workflows/debug.md +2 -0
  103. package/pp-core/workflows/diagnose-issues.md +1 -1
  104. package/pp-core/workflows/discovery-sprint/charter.md +114 -0
  105. package/pp-core/workflows/discovery-sprint/prefill.md +1 -1
  106. package/pp-core/workflows/discovery-sprint/prototype.md +4 -4
  107. package/pp-core/workflows/discovery-sprint/session-13-gates.md +11 -1
  108. package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +54 -7
  109. package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +31 -7
  110. package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +64 -37
  111. package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +12 -3
  112. package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +45 -22
  113. package/pp-core/workflows/discovery-sprint/sessions/06-security.md +30 -15
  114. package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +43 -25
  115. package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +63 -24
  116. package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +28 -13
  117. package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +35 -16
  118. package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +32 -8
  119. package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +21 -18
  120. package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +29 -12
  121. package/pp-core/workflows/discovery-sprint/vault-context.md +14 -3
  122. package/pp-core/workflows/discovery-sprint.md +23 -23
  123. package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +81 -29
  124. package/pp-core/workflows/discovery-to-build/steps/{screen-inventory-gate.md → handoff-gates.md} +111 -3
  125. package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +12 -9
  126. package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +27 -26
  127. package/pp-core/workflows/discovery-to-build.md +70 -34
  128. package/pp-core/workflows/discuss-phase/discovery.md +83 -0
  129. package/pp-core/workflows/discuss-phase/templates/context.md +4 -0
  130. package/pp-core/workflows/discuss-phase-assumptions.md +5 -3
  131. package/pp-core/workflows/discuss-phase.md +18 -10
  132. package/pp-core/workflows/docs-update.md +1 -0
  133. package/pp-core/workflows/eval-review.md +2 -1
  134. package/pp-core/workflows/execute-phase/steps/check-query-gate.md +7 -6
  135. package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +1 -0
  136. package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +1 -0
  137. package/pp-core/workflows/execute-phase/steps/vault-fetch.md +12 -0
  138. package/pp-core/workflows/execute-phase.md +19 -7
  139. package/pp-core/workflows/execute-plan.md +18 -7
  140. package/pp-core/workflows/explore.md +1 -1
  141. package/pp-core/workflows/forensics.md +4 -2
  142. package/pp-core/workflows/health.md +1 -0
  143. package/pp-core/workflows/import.md +2 -1
  144. package/pp-core/workflows/ingest-docs.md +1 -0
  145. package/pp-core/workflows/init/steps/client-context-capture.md +1 -0
  146. package/pp-core/workflows/init/steps/github-provisioning.md +145 -0
  147. package/pp-core/workflows/init/steps/gitlab-provisioning.md +18 -4
  148. package/pp-core/workflows/init/steps/reachability-outcome.md +3 -1
  149. package/pp-core/workflows/init.md +56 -66
  150. package/pp-core/workflows/map-codebase.md +5 -1
  151. package/pp-core/workflows/milestone-summary.md +2 -1
  152. package/pp-core/workflows/mvp-phase.md +1 -0
  153. package/pp-core/workflows/new-milestone.md +9 -5
  154. package/pp-core/workflows/new-project.md +12 -11
  155. package/pp-core/workflows/new-workspace.md +1 -0
  156. package/pp-core/workflows/next.md +2 -1
  157. package/pp-core/workflows/pause-work.md +6 -4
  158. package/pp-core/workflows/plan-milestone-gaps.md +3 -1
  159. package/pp-core/workflows/plan-phase/steps/chunked-planning.md +1 -1
  160. package/pp-core/workflows/plan-phase/steps/discovery-extract.md +41 -0
  161. package/pp-core/workflows/plan-phase.md +36 -16
  162. package/pp-core/workflows/plan-review-convergence.md +2 -0
  163. package/pp-core/workflows/plant-seed.md +4 -2
  164. package/pp-core/workflows/pr-branch.md +2 -0
  165. package/pp-core/workflows/progress.md +5 -0
  166. package/pp-core/workflows/quick.md +11 -4
  167. package/pp-core/workflows/reapply-patches.md +3 -0
  168. package/pp-core/workflows/remove-phase.md +1 -1
  169. package/pp-core/workflows/resume-project.md +3 -0
  170. package/pp-core/workflows/review.md +7 -1
  171. package/pp-core/workflows/secure-phase.md +2 -1
  172. package/pp-core/workflows/session-report.md +1 -0
  173. package/pp-core/workflows/ship.md +8 -1
  174. package/pp-core/workflows/sketch-wrap-up.md +1 -1
  175. package/pp-core/workflows/sketch.md +3 -2
  176. package/pp-core/workflows/spec-phase.md +2 -1
  177. package/pp-core/workflows/spike-wrap-up.md +1 -1
  178. package/pp-core/workflows/spike.md +4 -3
  179. package/pp-core/workflows/sync-skills.md +5 -2
  180. package/pp-core/workflows/thread.md +3 -2
  181. package/pp-core/workflows/transition.md +6 -0
  182. package/pp-core/workflows/ui-phase.md +3 -1
  183. package/pp-core/workflows/ui-review.md +2 -1
  184. package/pp-core/workflows/ultraplan-phase.md +1 -0
  185. package/pp-core/workflows/undo.md +14 -10
  186. package/pp-core/workflows/update.md +5 -0
  187. package/pp-core/workflows/validate-phase.md +6 -3
  188. package/pp-core/workflows/vault-review.md +7 -1
  189. package/pp-core/workflows/vault-write-back.md +5 -0
  190. package/pp-core/workflows/verify-phase.md +5 -0
  191. package/pp-core/workflows/verify-work.md +8 -1
  192. package/scripts/prompt-injection-scan.sh +1 -1
  193. package/scripts/sync-runtime-launcher.cjs +115 -5
  194. package/pp-core/workflows/discovery-sprint/templates/context-template.md +0 -266
@@ -39,7 +39,7 @@
39
39
  * has already sanitised.
40
40
  */
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.VAULT_INDEX_WITHHELD_KINDS = exports.VAULT_INDEX_UNCHECKED_CAUSES = exports.VAULT_INDEX_UNPARSEABLE_CAUSES = exports.VAULT_INDEX_REASON = exports.DISPLAY_UNSAFE_TEXT_CHAR_RE = exports.VAULT_BINDING_NOTE = exports.VAULT_PATH_REASONS = exports.VAULT_INDEX_LIST_BOUND = exports.VAULT_TEXT_MAX = exports.VAULT_INDEX_GRAMMAR = exports.VAULT_SECTION_HEADING_PREFIX = exports.VAULT_USED_WHEN_RE = exports.VAULT_USED_WHEN_MARKER = exports.VAULT_ENTRY_SEPARATOR = exports.VAULT_ENTRY_PREFIX = exports.VAULT_INDEX_BYTE_CAP = exports.VAULT_INDEX_FILENAME = void 0;
42
+ exports.VAULT_INDEX_WITHHELD_KINDS = exports.VAULT_INDEX_UNCHECKED_CAUSES = exports.VAULT_INDEX_UNPARSEABLE_CAUSES = exports.VAULT_INDEX_REASON = exports.DISPLAY_UNSAFE_TEXT_CHAR_RE = exports.VAULT_BINDING_NOTE = exports.VAULT_PATH_REASONS = exports.VAULT_INDEX_LIST_BOUND = exports.VAULT_TEXT_MAX = exports.VAULT_INDEX_GRAMMAR = exports.VAULT_CROSS_CUTTING_MARK = exports.VAULT_SECTION_HEADING_PREFIX = exports.VAULT_USED_WHEN_RE = exports.VAULT_USED_WHEN_MARKER = exports.VAULT_ENTRY_SEPARATOR = exports.VAULT_ENTRY_PREFIX = exports.VAULT_INDEX_BYTE_CAP = exports.VAULT_INDEX_FILENAME = void 0;
43
43
  exports.classifyVaultPath = classifyVaultPath;
44
44
  exports.isSafeVaultPath = isSafeVaultPath;
45
45
  exports.splitVaultEntryLine = splitVaultEntryLine;
@@ -74,6 +74,13 @@ exports.VAULT_USED_WHEN_MARKER = '; used when ';
74
74
  exports.VAULT_USED_WHEN_RE = /;\s+used when\s+/i;
75
75
  /** A section is opened by a level-2 ATX heading at column 0. */
76
76
  exports.VAULT_SECTION_HEADING_PREFIX = '## ';
77
+ /**
78
+ * The cross-cutting mark (issue #12, items 16-17): an entry whose remainder
79
+ * starts with it holds a rule every Discovery session must read — approval
80
+ * routes, folder layout, naming — whatever the session's topics are. It is
81
+ * stripped from the description and recorded as `crossCutting: true`.
82
+ */
83
+ exports.VAULT_CROSS_CUTTING_MARK = '[cross-cutting]';
77
84
  /** The six grammar values bundled, frozen, for a consumer that quotes the grammar whole. */
78
85
  exports.VAULT_INDEX_GRAMMAR = Object.freeze({
79
86
  filename: exports.VAULT_INDEX_FILENAME,
@@ -219,16 +226,22 @@ const LEVEL1_HEADING_RE = /^#(?:[ \t].*)?$/;
219
226
  // D-28: an entry prefix indented 1-3 spaces is a near-miss, not a nested list.
220
227
  const INDENTED_ENTRY_RE = /^ {1,3}- `/;
221
228
  function buildEntry(section, path, rest, line) {
222
- const m = exports.VAULT_USED_WHEN_RE.exec(rest);
229
+ // The mark is read off the front of the remainder; a mark with nothing after
230
+ // it stays text, so the description is never left empty.
231
+ const lead = rest.trimStart();
232
+ const marked = lead.startsWith(exports.VAULT_CROSS_CUTTING_MARK) && lead.slice(exports.VAULT_CROSS_CUTTING_MARK.length).trim() !== '';
233
+ const body = marked ? lead.slice(exports.VAULT_CROSS_CUTTING_MARK.length) : rest;
234
+ const flag = marked ? { crossCutting: true } : {};
235
+ const m = exports.VAULT_USED_WHEN_RE.exec(body);
223
236
  if (m) {
224
- const usedWhen = rest.slice(m.index + m[0].length).trim();
237
+ const usedWhen = body.slice(m.index + m[0].length).trim();
225
238
  if (usedWhen !== '') {
226
- return { section, path, description: rest.slice(0, m.index).trim(), usedWhen, line };
239
+ return { section, path, description: body.slice(0, m.index).trim(), usedWhen, ...flag, line };
227
240
  }
228
241
  }
229
242
  // No clause, or an empty one: the whole remainder is the description and the
230
243
  // key is omitted — a clause is never invented and text is never dropped.
231
- return { section, path, description: rest.trim(), line };
244
+ return { section, path, description: body.trim(), ...flag, line };
232
245
  }
233
246
  /**
234
247
  * Parse an INDEX.md text tolerantly. Never throws; a non-string is read as
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.REVIEW_FILES_CAP = exports.REVIEW_VERDICTS_HEADING = exports.REVIEW_ARTIFACT_MARKER = exports.REVIEW_ARTIFACT_SUFFIX = exports.REVIEW_VERDICTS = void 0;
6
+ exports.DEVIATIONS_FILE = exports.REVIEW_FILES_CAP = exports.REVIEW_VERDICTS_HEADING = exports.REVIEW_ARTIFACT_MARKER = exports.REVIEW_ARTIFACT_SUFFIX = exports.REVIEW_VERDICTS = void 0;
7
+ exports.readDeviations = readDeviations;
7
8
  exports.deriveStandardsReview = deriveStandardsReview;
8
9
  exports.formatReviewCounts = formatReviewCounts;
9
10
  exports.validateReviewArtifact = validateReviewArtifact;
@@ -59,6 +60,8 @@ exports.REVIEW_VERDICTS_HEADING = '## Standard Verdicts';
59
60
  exports.REVIEW_FILES_CAP = 200;
60
61
  /** Bound on a git query a review makes. */
61
62
  const REVIEW_GIT_TIMEOUT_MS = 30_000;
63
+ /** Where the Discovery Sprint records agreed departures from the vault. */
64
+ exports.DEVIATIONS_FILE = '.planning/discovery/_deviations.md';
62
65
  // ─── Derivation ──────────────────────────────────────────────────────────────
63
66
  const PLAN_RE = /(^|-)PLAN\.md$/;
64
67
  const SUMMARY_RE = /(^|-)SUMMARY\.md$/;
@@ -73,6 +76,47 @@ function readBounded(abs) {
73
76
  return null;
74
77
  }
75
78
  }
79
+ /**
80
+ * The deviations on file, read by header name so Batch 3's full grammar can
81
+ * add or reorder columns without touching this reader. `gap` is set when the
82
+ * file exists but yields no readable table with both join columns.
83
+ */
84
+ function readDeviations(root) {
85
+ const abs = node_path_1.default.join(root, exports.DEVIATIONS_FILE);
86
+ if (!node_fs_1.default.existsSync(abs))
87
+ return { deviations: [], gap: null };
88
+ const text = readBounded(abs);
89
+ if (text === null)
90
+ return { deviations: [], gap: `${exports.DEVIATIONS_FILE} could not be read (unreadable or over the size cap), so deviations on file are unknown` };
91
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
92
+ const table = require('./discovery-table.cjs');
93
+ const deviations = [];
94
+ let joinable = false;
95
+ for (const section of table.splitSections(text.split(/\r?\n/), 2)) {
96
+ for (const tbl of table.parseTables(section.body)) {
97
+ const names = tbl.header.map((h) => h.trim().toLowerCase());
98
+ const col = (name) => names.indexOf(name);
99
+ const standard = col('standard');
100
+ const status = col('status');
101
+ if (standard === -1 || status === -1)
102
+ continue;
103
+ joinable = true;
104
+ const cell = (row, i) => (i === -1 ? '' : (row.cells[i] ?? '').trim());
105
+ for (const row of tbl.rows) {
106
+ deviations.push({
107
+ id: cell(row, col('id')),
108
+ standard: cell(row, standard),
109
+ status: cell(row, status),
110
+ decision: cell(row, col('decision')),
111
+ approver: cell(row, col('approver')),
112
+ });
113
+ }
114
+ }
115
+ }
116
+ if (!joinable)
117
+ return { deviations: [], gap: `${exports.DEVIATIONS_FILE} carries no table with Standard and Status columns, so deviations on file are unknown` };
118
+ return { deviations, gap: null };
119
+ }
76
120
  function consultedText(entry, hasSummary) {
77
121
  if (!hasSummary)
78
122
  return 'no summary';
@@ -206,6 +250,9 @@ function deriveStandardsReview(root, cwd, positional, opts = {}) {
206
250
  });
207
251
  }
208
252
  }
253
+ const onFile = readDeviations(root);
254
+ if (onFile.gap !== null)
255
+ gaps.push(onFile.gap);
209
256
  if (plans.length === 0 || documents.size === 0) {
210
257
  return {
211
258
  status: 'no-records',
@@ -216,9 +263,10 @@ function deriveStandardsReview(root, cwd, positional, opts = {}) {
216
263
  plans,
217
264
  documents: [],
218
265
  gaps,
266
+ deviations: onFile.deviations,
219
267
  };
220
268
  }
221
- return { status: 'ok', phaseDir: phase.rel, plans, documents: [...documents.values()], gaps };
269
+ return { status: 'ok', phaseDir: phase.rel, plans, documents: [...documents.values()], gaps, deviations: onFile.deviations };
222
270
  }
223
271
  /** The counts line, in verdict order: `p PASS · f FAIL · n N-A · i INCONCLUSIVE (of N documents)`. */
224
272
  function formatReviewCounts(counts) {
@@ -109,6 +109,7 @@ function vaultUpdate(cwd, opts = {}) {
109
109
  from: null,
110
110
  to: null,
111
111
  latest: null,
112
+ newer: null,
112
113
  available: [],
113
114
  fetch: null,
114
115
  nextStep: null,
@@ -152,6 +153,9 @@ function vaultUpdate(cwd, opts = {}) {
152
153
  tags = [...new Set(tags)].sort((a, b) => (0, semver_compare_cjs_1.compareSemverCore)(b, a));
153
154
  result.available = tags.slice(0, exports.AVAILABLE_TAGS_CAP);
154
155
  result.latest = tags[0] ?? null;
156
+ if (result.from && result.latest && (0, semver_compare_cjs_1.isStableTripletSemver)(result.from) && (0, semver_compare_cjs_1.compareSemverCore)(result.latest, result.from) > 0) {
157
+ result.newer = result.latest;
158
+ }
155
159
  if (tags.length === 0) {
156
160
  return fail('no_tags', `The vault repo "${redactCloneTarget(repo)}" has no stable vX.Y.Z tag to pin to.`);
157
161
  }
@@ -2809,7 +2809,7 @@ function resolveRefDrift({ cwd, tier, cachedRef }) {
2809
2809
  // DEVIATION from plan 08-01's step 3, recorded so the next reader does not
2810
2810
  // "restore" it: the plan prescribed copying the miss branch's call-site-
2811
2811
  // scoped `require('./git-provider.cjs')` / `require('./vault-coordinates.cjs')`
2812
- // pair from src/check-command-router.cts:1558-1565. That shape exists THERE
2812
+ // pair from src/check-command-router.cts:1587-1594. That shape exists THERE
2813
2813
  // because the check router does not otherwise import those leaves, so the
2814
2814
  // require genuinely keeps its module graph narrow. It buys nothing HERE:
2815
2815
  // this module already imports `resolveProvider` (:40) and
@@ -2833,7 +2833,7 @@ function resolveRefDrift({ cwd, tier, cachedRef }) {
2833
2833
  // UNCONDITIONALLY, including when `repo` is null, so an ungated
2834
2834
  // comparison reports phantom drift on every project that configures no
2835
2835
  // client vault at all. The miss branch argues the same point at
2836
- // src/check-command-router.cts:1567-1570, where it keeps the truthful
2836
+ // src/check-command-router.cts:1596-1599, where it keeps the truthful
2837
2837
  // `(no ref pinned)` placeholder for the same reason.
2838
2838
  //
2839
2839
  // D-08-39(a): a BLANK or WHITESPACE-ONLY `client.vaultRepo` is not a
@@ -19,8 +19,8 @@
19
19
  * state signal-resume Remove WAITING.json signal
20
20
  * resolve-model <agent-type> Get model for agent based on profile
21
21
  * find-phase <phase> Find phase directory by number
22
- * commit <message> [--files f1 f2] [--no-verify] Commit planning docs
23
- * commit-to-subrepo <msg> --files f1 f2 Route commits to sub-repos
22
+ * commit <message> [--files f1 f2] [--phase id] [--quick id] [--no-verify] Commit planning docs
23
+ * commit-to-subrepo <msg> --files f1 f2 [--phase id] Route commits to sub-repos
24
24
  * verify-summary <path> Verify a SUMMARY.md file
25
25
  * generate-slug <text> Convert text to URL-safe slug
26
26
  * current-timestamp [format] Get timestamp (full|date|filename)
@@ -1158,6 +1158,10 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1158
1158
  }
1159
1159
 
1160
1160
  case 'commit': {
1161
+ // --phase <id> / --quick <id> add a `Phase:` / `Quick:` trailer; pulled out
1162
+ // first so their values are never read as message words or --files paths.
1163
+ const trailerArgs = commands.extractCommitTrailerArgs(args);
1164
+ args = trailerArgs.args;
1161
1165
  const amend = args.includes('--amend');
1162
1166
  const noVerify = args.includes('--no-verify');
1163
1167
  const filesIndex = args.indexOf('--files');
@@ -1168,7 +1172,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1168
1172
  const messageArgs = args.slice(1, endIndex).filter(a => !a.startsWith('--'));
1169
1173
  const message = messageArgs.join(' ') || undefined;
1170
1174
  const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
1171
- commands.cmdCommit(cwd, message, files, raw, amend, noVerify);
1175
+ commands.cmdCommit(cwd, message, files, raw, amend, noVerify, { phase: trailerArgs.phase, quick: trailerArgs.quick });
1172
1176
  break;
1173
1177
  }
1174
1178
 
@@ -1178,10 +1182,12 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
1178
1182
  }
1179
1183
 
1180
1184
  case 'commit-to-subrepo': {
1185
+ const trailerArgs = commands.extractCommitTrailerArgs(args);
1186
+ args = trailerArgs.args;
1181
1187
  const message = args[1];
1182
1188
  const filesIndex = args.indexOf('--files');
1183
1189
  const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
1184
- commands.cmdCommitToSubrepo(cwd, message, files, raw);
1190
+ commands.cmdCommitToSubrepo(cwd, message, files, raw, { phase: trailerArgs.phase, quick: trailerArgs.quick });
1185
1191
  break;
1186
1192
  }
1187
1193
 
@@ -2558,13 +2564,155 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
2558
2564
  } catch (vaultCheckErr) {
2559
2565
  error(`discovery vault-check: ${vaultCheckErr && vaultCheckErr.message ? vaultCheckErr.message : String(vaultCheckErr)}`, ERROR_REASON.UNKNOWN);
2560
2566
  }
2567
+ } else if (subcommand === 'charter-check') {
2568
+ // discovery charter-check [--require-signed]
2569
+ //
2570
+ // Structural check of docs/discovery/PROJECT-CHARTER.md: frontmatter
2571
+ // enums, the twenty `<!-- charter:<key> -->` sections in order, every
2572
+ // cited ID resolving to a session table or _requirements.md row, the
2573
+ // Go / No-Go table equal to Session 13's, must-resolve open items
2574
+ // closed or listed as conditions, sign-off rows agreeing with status.
2575
+ // --require-signed also fails (discovery_charter_unsigned) unless the
2576
+ // status is Signed or Signed with conditions — the handoff gate's arm.
2577
+ const charter = require('./lib/discovery-charter.cjs');
2578
+ try {
2579
+ charter.cmdDiscoveryCharterCheck(cwd, args, raw);
2580
+ } catch (charterCheckErr) {
2581
+ error(`discovery charter-check: ${charterCheckErr && charterCheckErr.message ? charterCheckErr.message : String(charterCheckErr)}`, ERROR_REASON.UNKNOWN);
2582
+ }
2583
+ } else if (subcommand === 'entity-check') {
2584
+ // discovery entity-check [--require-complete] — _entities.md structure
2585
+ // (improvement plan, Batch 2): named entities, closed lifecycle and
2586
+ // classification cells, attributes per entity, Mermaid cardinalities,
2587
+ // step-matrix data objects resolving. --require-complete also fails on
2588
+ // pending `—` cells (discovery_entity_check_incomplete).
2589
+ const entities = require('./lib/discovery-entities.cjs');
2590
+ try {
2591
+ entities.cmdDiscoveryEntityCheck(cwd, args, raw);
2592
+ } catch (entityCheckErr) {
2593
+ error(`discovery entity-check: ${entityCheckErr && entityCheckErr.message ? entityCheckErr.message : String(entityCheckErr)}`, ERROR_REASON.UNKNOWN);
2594
+ }
2595
+ } else if (subcommand === 'process-check') {
2596
+ // discovery process-check [--require-cited] — _processes.md structure:
2597
+ // PRC-/MP- sequences, Replaces resolving, one swimlane and one step
2598
+ // matrix per main process, actors, data objects, requirement IDs.
2599
+ // --require-cited also fails on a step citing no requirement.
2600
+ const processes = require('./lib/discovery-processes.cjs');
2601
+ try {
2602
+ processes.cmdDiscoveryProcessCheck(cwd, args, raw);
2603
+ } catch (processCheckErr) {
2604
+ error(`discovery process-check: ${processCheckErr && processCheckErr.message ? processCheckErr.message : String(processCheckErr)}`, ERROR_REASON.UNKNOWN);
2605
+ }
2606
+ } else if (subcommand === 'rules-check') {
2607
+ // discovery rules-check — _rules.md structure: BR- sequence, layer
2608
+ // vocabulary, every rule citing an existing requirement and an existing
2609
+ // entity or process.
2610
+ const rules = require('./lib/discovery-rules.cjs');
2611
+ try {
2612
+ rules.cmdDiscoveryRulesCheck(cwd, args, raw);
2613
+ } catch (rulesCheckErr) {
2614
+ error(`discovery rules-check: ${rulesCheckErr && rulesCheckErr.message ? rulesCheckErr.message : String(rulesCheckErr)}`, ERROR_REASON.UNKNOWN);
2615
+ }
2616
+ } else if (subcommand === 'stories-check') {
2617
+ // discovery stories-check — _stories.md structure (improvement plan,
2618
+ // Batch 4): US- sequence, EPIC shape, every story citing an existing
2619
+ // requirement, source step resolving, every MVP domain requirement
2620
+ // carrying at least one story.
2621
+ const stories = require('./lib/discovery-stories.cjs');
2622
+ try {
2623
+ stories.cmdDiscoveryStoriesCheck(cwd, args, raw);
2624
+ } catch (storiesCheckErr) {
2625
+ error(`discovery stories-check: ${storiesCheckErr && storiesCheckErr.message ? storiesCheckErr.message : String(storiesCheckErr)}`, ERROR_REASON.UNKNOWN);
2626
+ }
2627
+ } else if (subcommand === 'phases-check') {
2628
+ // discovery phases-check — _phases.md structure (improvement plan,
2629
+ // Batch 4): contiguous phases, requirement IDs resolving and not
2630
+ // dropped, dependencies on earlier phases, every MVP requirement in
2631
+ // exactly one phase.
2632
+ const phasesCheck = require('./lib/discovery-phases.cjs');
2633
+ try {
2634
+ phasesCheck.cmdDiscoveryPhasesCheck(cwd, args, raw);
2635
+ } catch (phasesCheckErr) {
2636
+ error(`discovery phases-check: ${phasesCheckErr && phasesCheckErr.message ? phasesCheckErr.message : String(phasesCheckErr)}`, ERROR_REASON.UNKNOWN);
2637
+ }
2638
+ } else if (subcommand === 'for-phase') {
2639
+ // discovery for-phase <N> [--markdown | --conformance-template |
2640
+ // --verify <file>] — the rows of every canonical artifact that cite
2641
+ // the phase's requirement IDs as one extract between
2642
+ // <!-- discovery:auto --> markers (improvement plan, Batch 4, R1);
2643
+ // the verifier's conformance block and its check (R3). Fails loud on
2644
+ // artifact structure only; no discovery package → exit 0, empty.
2645
+ const forPhase = require('./lib/discovery-for-phase.cjs');
2646
+ try {
2647
+ forPhase.cmdDiscoveryForPhase(cwd, args, raw);
2648
+ } catch (forPhaseErr) {
2649
+ error(`discovery for-phase: ${forPhaseErr && forPhaseErr.message ? forPhaseErr.message : String(forPhaseErr)}`, ERROR_REASON.UNKNOWN);
2650
+ }
2651
+ } else if (subcommand === 'roles-check') {
2652
+ // discovery roles-check — _roles.md structure (improvement plan,
2653
+ // Batch 3): every role a persona, a step-matrix actor or System;
2654
+ // permissions naming a known role, an existing entity, lowercase
2655
+ // operations and at least one requirement; screen rows resolving.
2656
+ const roles = require('./lib/discovery-access.cjs');
2657
+ try {
2658
+ roles.cmdDiscoveryRolesCheck(cwd, args, raw);
2659
+ } catch (rolesCheckErr) {
2660
+ error(`discovery roles-check: ${rolesCheckErr && rolesCheckErr.message ? rolesCheckErr.message : String(rolesCheckErr)}`, ERROR_REASON.UNKNOWN);
2661
+ }
2662
+ } else if (subcommand === 'integrations-check') {
2663
+ // discovery integrations-check [--require-cited] — _integrations.md
2664
+ // structure: IF- sequence, direction vocabulary, contract and error
2665
+ // rows naming a known interface, step-matrix IF- tokens resolving.
2666
+ // --require-cited also fails on an uncited interface and on a step
2667
+ // naming a system without an IF- (discovery_integrations_uncited).
2668
+ const integrations = require('./lib/discovery-integrations.cjs');
2669
+ try {
2670
+ integrations.cmdDiscoveryIntegrationsCheck(cwd, args, raw);
2671
+ } catch (integrationsCheckErr) {
2672
+ error(`discovery integrations-check: ${integrationsCheckErr && integrationsCheckErr.message ? integrationsCheckErr.message : String(integrationsCheckErr)}`, ERROR_REASON.UNKNOWN);
2673
+ }
2674
+ } else if (subcommand === 'migration-check') {
2675
+ // discovery migration-check [--require-cited] — _migration.md
2676
+ // structure: MG- sequence, source system in Session 2's landscape,
2677
+ // entities resolving, requirement IDs resolving. --require-cited
2678
+ // also fails on an uncited row (discovery_migration_uncited).
2679
+ const migration = require('./lib/discovery-migration.cjs');
2680
+ try {
2681
+ migration.cmdDiscoveryMigrationCheck(cwd, args, raw);
2682
+ } catch (migrationCheckErr) {
2683
+ error(`discovery migration-check: ${migrationCheckErr && migrationCheckErr.message ? migrationCheckErr.message : String(migrationCheckErr)}`, ERROR_REASON.UNKNOWN);
2684
+ }
2685
+ } else if (subcommand === 'session-check') {
2686
+ // discovery session-check <N> [<N>…] | --all — every Notes Template
2687
+ // section of the session content file present exactly once in
2688
+ // session-NN.md, each table with a data row or the literal
2689
+ // `None identified in Discovery.`; --all covers the Completed sessions.
2690
+ const sessionCheck = require('./lib/discovery-session-check.cjs');
2691
+ try {
2692
+ sessionCheck.cmdDiscoverySessionCheck(cwd, args, raw);
2693
+ } catch (sessionCheckErr) {
2694
+ error(`discovery session-check: ${sessionCheckErr && sessionCheckErr.message ? sessionCheckErr.message : String(sessionCheckErr)}`, ERROR_REASON.UNKNOWN);
2695
+ }
2696
+ } else if (subcommand === 'deviations-check') {
2697
+ // discovery deviations-check [--require-approved] — _deviations.md
2698
+ // structure: DEV- sequence, a [vault: path] standard present in the
2699
+ // cache, reversible and status vocabularies, approver in Session 1's
2700
+ // Approval Matrix, decision ID shape. --require-approved also fails
2701
+ // on a hard-to-reverse deviation still Proposed
2702
+ // (discovery_deviations_unapproved).
2703
+ const deviations = require('./lib/discovery-deviations.cjs');
2704
+ try {
2705
+ deviations.cmdDiscoveryDeviationsCheck(cwd, args, raw);
2706
+ } catch (deviationsCheckErr) {
2707
+ error(`discovery deviations-check: ${deviationsCheckErr && deviationsCheckErr.message ? deviationsCheckErr.message : String(deviationsCheckErr)}`, ERROR_REASON.UNKNOWN);
2708
+ }
2561
2709
  } else {
2562
2710
  // WIRING, NOT HELP TEXT: tests/discovery-artifacts.test.cjs derives each
2563
2711
  // subcommand spelling by regexing `<namespace>_<subcommand>_failed` out of
2564
2712
  // the engine's exported exit identity, splits this string on commas and
2565
2713
  // asserts membership. A spelling missing here is a command the engine
2566
2714
  // claims to be and the router does not dispatch.
2567
- error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check, vault-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
2715
+ error('Unknown discovery subcommand. Available: rollup, input-check, research-check, screen-check, element-sweep, rebaseline-check, vault-check, charter-check, entity-check, process-check, rules-check, stories-check, phases-check, for-phase, roles-check, integrations-check, migration-check, deviations-check, session-check', ERROR_REASON.SDK_UNKNOWN_COMMAND);
2568
2716
  }
2569
2717
  break;
2570
2718
  }
@@ -11,7 +11,7 @@
11
11
  "repo": "pareto000/Pareto-Platform"
12
12
  },
13
13
  "client": {
14
- "vaultRepo": "pareto000/Pareto-Vault",
14
+ "vaultRepo": "pareto-ooo/pareto-vault-releases",
15
15
  "vaultHost": "github.com"
16
16
  },
17
17
  "provisioning": {
@@ -83,7 +83,7 @@ grep -q "^handled=${PHASE_NUM}:" "$RUN_STATE" 2>/dev/null ||
83
83
  printf 'handled=%s:%s\n' "$PHASE_NUM" "$OUTCOME" >> "$RUN_STATE"
84
84
  PADDED_PHASE=$(basename "$PHASE_DIR" | cut -d- -f1)
85
85
  if [ -f "$REPORT" ]; then
86
- pp_run query commit "docs(${PADDED_PHASE}): autonomous report" --files "$REPORT"
86
+ pp_run query commit "docs(planning): autonomous report" --phase "${PADDED_PHASE}" --files "$REPORT"
87
87
  fi
88
88
  ```
89
89
 
@@ -400,6 +400,7 @@ phase. Printed by the lifecycle single-phase banner, the STOPPED banner and the
400
400
  Used by `autonomous.md` §3a when `workflow.skip_discuss` is true. Get phase details:
401
401
 
402
402
  ```bash
403
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
403
404
  DETAIL=$(pp_run query roadmap.get-phase ${PHASE_NUM})
404
405
  ```
405
406
 
@@ -452,7 +453,7 @@ None — discuss phase skipped.
452
453
  Commit the minimal context:
453
454
 
454
455
  ```bash
455
- pp_run query commit "docs(${padded_phase}): auto-generated context (discuss skipped)" --files "${phase_dir}/${padded_phase}-CONTEXT.md"
456
+ pp_run query commit "docs(planning): auto-generated context (discuss skipped)" --phase "${padded_phase}" --files "${phase_dir}/${padded_phase}-CONTEXT.md"
456
457
  ```
457
458
 
458
459
  ## Banner math
@@ -109,6 +109,7 @@ Skill(skill="pp-complete-milestone", args="${milestone_version}")
109
109
  After complete-milestone returns, verify it produced output:
110
110
 
111
111
  ```bash
112
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
112
113
  ls .planning/milestones/v${milestone_version}-ROADMAP.md 2>/dev/null || true
113
114
  ```
114
115
 
@@ -5,6 +5,7 @@ Smart discuss is the autonomous-optimized variant of `pp-discuss-phase`. It prop
5
5
  **Inputs:** `PHASE_NUM` from execute_phase. Run init to get phase paths:
6
6
 
7
7
  ```bash
8
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
8
9
  PHASE_STATE=$(pp-tools query init.phase-op ${PHASE_NUM})
9
10
  ```
10
11
 
@@ -34,6 +35,7 @@ Extract from these:
34
35
  **Read all prior CONTEXT.md files:**
35
36
 
36
37
  ```bash
38
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
37
39
  (find "$(dirname "$STATE_PATH")/phases" -name "*-CONTEXT.md" 2>/dev/null || true) | sort
38
40
  ```
39
41
 
@@ -68,6 +70,7 @@ Lightweight codebase scan to inform grey area identification and proposals. Keep
68
70
  **Check for existing codebase maps:**
69
71
 
70
72
  ```bash
73
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
71
74
  ls .planning/codebase/*.md 2>/dev/null || true
72
75
  ```
73
76
 
@@ -96,6 +99,7 @@ Read the 3-5 most relevant files to understand existing patterns.
96
99
  **Get phase details:**
97
100
 
98
101
  ```bash
102
+ [ -z "${ZSH_VERSION:-}" ] || emulate -R sh
99
103
  DETAIL=$(pp-tools query roadmap.get-phase ${PHASE_NUM})
100
104
  ```
101
105
 
@@ -270,7 +274,7 @@ Write the file.
270
274
  **Commit:**
271
275
 
272
276
  ```bash
273
- pp-tools query commit "docs(${PADDED_PHASE}): smart discuss context" --files "${phase_dir}/${padded_phase}-CONTEXT.md"
277
+ pp_run query commit "docs(planning): smart discuss context" --phase "${PADDED_PHASE}" --files "${phase_dir}/${padded_phase}-CONTEXT.md"
274
278
  ```
275
279
 
276
280
  Display confirmation:
@@ -0,0 +1,112 @@
1
+ # Discovery Access Model — `_roles.md` Template & Parser Contract
2
+
3
+ Single home for the roles artifact the build reads instead of Session 7's notes (Discovery Sprint
4
+ improvement plan, Batch 3): `_roles.md` holds the roles, what each may do to which entity, and which
5
+ screens each may open. Its gate is `pp_run query discovery.roles-check`. This file is Read lazily; it
6
+ is never `@`-imported.
7
+
8
+ ## When the file appears
9
+
10
+ | Session | `_roles.md` |
11
+ |---|---|
12
+ | 7 | created from the template; topic 1 writes `## Roles` and `## Permissions`, topic 4 writes `## Screens`; `roles-check` in the session |
13
+ | 8 | `roles-check` in the Closing Step (reporting) |
14
+ | 13 | in the Step 0 gates (reporting) |
15
+ | handoff | `roles-check` hard-blocks |
16
+
17
+ Session 7's **Roles** section keeps a reading summary and points here: the canonical rows live in
18
+ the artifact, exactly as the requirements table lives in `_requirements.md`. The old preset matrix
19
+ (Admin / Manager / User / Viewer with one CRUD column per entity) is gone: a role is whatever the
20
+ client's process needs, and a permission is one row per role and entity with the operations as a
21
+ list, so a build phase can read exactly the rows that cite its requirements.
22
+
23
+ ## Grammar
24
+
25
+ - **A role is an actor the process already has.** Every `## Roles` row names a Session 2 persona
26
+ (spelled as the User Personas table spells it), an actor a step matrix of `_processes.md` names,
27
+ or `System`. A role nobody plays in any step is a role the build cannot test; name the persona or
28
+ add the step. Unresolved while neither `session-02.md` nor `_processes.md` is on file.
29
+ - **Recovery path** is how the role is recovered when its last holder leaves or is locked out — the
30
+ vault's rule that no role may become unreachable (`[vault: <path>]` when the vault states it).
31
+ - **An operation is a lowercase verb** (`create`, `read`, `update`, `delete`, `assign`, `export`,
32
+ `approve`), comma-separated; the vocabulary is the product's, the shape is fixed.
33
+ - **An entity is a row of `_entities.md`**, one PascalCase token (`references/discovery-data-model.md`).
34
+ - **Condition** narrows a permission (`own team only`, `assignee or lead`); `—` is unconditional.
35
+ - **Requirements** names the `_requirements.md` IDs the permission realises — at least one; a
36
+ permission nothing requires is a question for the user, not a row.
37
+ - **A screen row is the authorization join**: the role may open the screen. `_screens.md` keeps its
38
+ own `Role` column as a reading aid (D-50); this table is the one the forbidden-path tests read.
39
+ - **Nothing is deleted.** A retired role keeps its row; a change after sign-off edits the row in
40
+ place and appends `→ amended by D-<ss>-<n>` (`references/discovery-id-conventions.md` § Precedence).
41
+
42
+ ## `_roles.md`
43
+
44
+ Illustration only — not part of the template, names invented:
45
+
46
+ | Role | Description | Scope | Recovery path | Source |
47
+ |---|---|---|---|---|
48
+ | Team lead | Runs a team's work and membership | Teams they lead | An operator with `Teams.Recover` names a member lead | S7 |
49
+
50
+ | Role | Entity | Operation | Condition | Requirements |
51
+ |---|---|---|---|---|
52
+ | Team lead | Membership | create, delete, update | own team only | K-13, K-14 |
53
+
54
+ | Role | Screen |
55
+ |---|---|
56
+ | Team lead | SCR-05 |
57
+
58
+ Bootstrap template — Session 7 copies it verbatim to `.planning/discovery/_roles.md`, replacing
59
+ `[PROJECT_NAME]`:
60
+
61
+ ```markdown
62
+ # Roles — [PROJECT_NAME]
63
+
64
+ **Conventions:** pp-core reference `references/discovery-access.md`. A role is a Session 2 persona, a step-matrix actor or `System`; one permission row per role and entity, operations as a comma list; a screen row says the role may open the screen. Rows are never deleted; a change after sign-off edits the row in place and appends `→ amended by D-<ss>-<n>`.
65
+
66
+ ## Roles
67
+
68
+ | Role | Description | Scope | Recovery path | Source |
69
+ |------|-------------|-------|---------------|--------|
70
+
71
+ ## Permissions
72
+
73
+ | Role | Entity | Operation | Condition | Requirements |
74
+ |------|--------|-----------|-----------|--------------|
75
+
76
+ ## Screens
77
+
78
+ | Role | Screen |
79
+ |------|--------|
80
+ ```
81
+
82
+ ### Parser contract — `pp_run query discovery.roles-check`
83
+
84
+ No positional path, no flags. Faults name the artifact, the 1-based line, a validated ID or name, a
85
+ section or column name and the reason code — never cell prose. Absent `_roles.md` → exit 0,
86
+ `present: false`.
87
+
88
+ | Cell / field | Grammar | Fault when violated |
89
+ |---|---|---|
90
+ | `## Roles`, `## Permissions`, `## Screens` | each once as a `##` heading holding one table with the template's columns | `missing_section`, `bad_table_shape` naming the section |
91
+ | Roles `Role` | non-empty, listed once; a Session 2 persona, a step-matrix `Actor` token or `System` | `empty_cell (Role)`, `duplicate_role`, `unknown_role` naming the role. **Not resolved** while neither `session-02.md` nor `_processes.md` is on file |
92
+ | Roles `Description`, `Scope`, `Recovery path` | non-empty (`—` is a pending cell the facilitator owns) | `empty_cell` naming the column |
93
+ | Roles `Source` | free text | **not mechanically checked** |
94
+ | Permissions / Screens `Role` | a `## Roles` row | `unknown_role` |
95
+ | Permissions `Entity` | one PascalCase token present in `_entities.md` § Entities when on file | `bad_entity`; `unknown_entity` naming the entity |
96
+ | Permissions `Operation` | a comma list of `^[a-z][a-z-]*$` tokens | `bad_operation` |
97
+ | Permissions `Condition` | free text (`—` allowed) | **not mechanically checked** |
98
+ | Permissions `Requirements` | at least one requirement ID, each present in `_requirements.md` when on file and parseable | `no_requirement`, `unknown_requirement_id` |
99
+ | Screens `Screen` | `SCR-\d{2}`, present in `_screens.md` § Screens when on file | `bad_screen`; `unknown_screen` naming the screen |
100
+ | `_requirements.md` present but unparseable | the rollup grammar is owned by `discovery rollup` | `requirements_unparseable`, once; IDs are then shape-checked only |
101
+ | a source present but unreadable | present-and-readable | `artifact_unreadable` naming the path |
102
+
103
+ **Exit identity.** `discovery_roles_check_failed`. Payload: `ok`, `present`, `file`, `roles`,
104
+ `permissions`, `screens`.
105
+
106
+ ## How the build reads it
107
+
108
+ `pp_run query discovery.for-phase <N>` selects the § Permissions rows citing the phase's requirement
109
+ IDs, then the § Roles row of every role those permissions name and the § Screens rows of those roles
110
+ and of the phase's screens (`references/discovery-build-consumption.md`). The conformance key is the
111
+ role name. The planner's auth phase reads the extract; the forbidden-path tests are written from the
112
+ screen rows and the permission conditions.