session-orchestrator 4.0.1 → 4.2.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 (145) hide show
  1. package/.agents/skills/session-plan/SKILL.md +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.codex-plugin/skills/session-plan/SKILL.md +1 -1
  6. package/.cursor/skills/session-plan/SKILL.md +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/CHANGELOG.md +57 -0
  9. package/README.md +55 -51
  10. package/agents/ux-evaluator.md +1 -1
  11. package/commands/close.md +3 -3
  12. package/commands/go.md +2 -0
  13. package/commands/memory-cleanup.md +4 -3
  14. package/commands/persona-panel.md +1 -1
  15. package/commands/release.md +4 -4
  16. package/commands/session.md +3 -2
  17. package/docs/README.md +4 -4
  18. package/docs/USER-GUIDE.md +115 -48
  19. package/docs/agent-authoring.md +2 -2
  20. package/docs/baseline.md +55 -1
  21. package/docs/ci-setup.md +1 -1
  22. package/docs/codex-setup.md +1 -0
  23. package/docs/components.md +2 -2
  24. package/docs/cursor-setup.md +1 -0
  25. package/docs/events-schema.md +4 -1
  26. package/docs/instruction-delivery.md +1 -1
  27. package/docs/memory-proposal-flow.md +3 -3
  28. package/docs/migration-v4.md +2 -2
  29. package/docs/owner-config-schema.md +74 -90
  30. package/docs/persona-panel.md +4 -4
  31. package/docs/pi-setup.md +1 -0
  32. package/docs/rule-authoring.md +13 -6
  33. package/docs/scope-collision-guard.md +2 -0
  34. package/docs/session-config-reference.md +55 -22
  35. package/docs/session-config-template.md +9 -5
  36. package/docs/vault-docs-architecture.md +4 -2
  37. package/hooks/_lib/hook-import-set.json +28 -3
  38. package/hooks/_lib/vcs-create-matcher.mjs +214 -16
  39. package/hooks/hooks-codex.json +1 -1
  40. package/hooks/hooks.json +1 -1
  41. package/hooks/pre-bash-issue-budget.mjs +123 -26
  42. package/hooks/subagent-telemetry.mjs +106 -20
  43. package/package.json +4 -4
  44. package/scripts/baseline-archetypes.mjs +28 -0
  45. package/scripts/ci/assert-coverage-green.mjs +100 -0
  46. package/scripts/lib/auto-dialectic.mjs +0 -68
  47. package/scripts/lib/baseline-archetypes.mjs +439 -0
  48. package/scripts/lib/build-live-signals.mjs +5 -6
  49. package/scripts/lib/config/issue-budget.mjs +68 -8
  50. package/scripts/lib/config/private-config-dir.mjs +3 -2
  51. package/scripts/lib/config/remote-hosts.mjs +2 -2
  52. package/scripts/lib/config-schema.mjs +79 -0
  53. package/scripts/lib/events.mjs +3 -3
  54. package/scripts/lib/file-lock.mjs +47 -5
  55. package/scripts/lib/issue-budget-reconcile.mjs +392 -0
  56. package/scripts/lib/issue-budget.mjs +76 -3
  57. package/scripts/lib/learnings/evolve-telemetry.mjs +1 -2
  58. package/scripts/lib/maintenance-due-banner.mjs +440 -0
  59. package/scripts/lib/owner-config.example.yaml +29 -46
  60. package/scripts/lib/owner-yaml.mjs +14 -13
  61. package/scripts/lib/project-hygiene.mjs +182 -6
  62. package/scripts/lib/quality-gate.mjs +13 -6
  63. package/scripts/lib/resource-probe/evaluate.mjs +19 -21
  64. package/scripts/lib/rules-sync.mjs +34 -4
  65. package/scripts/lib/session-close-backfill.mjs +182 -40
  66. package/scripts/lib/session-end/phase-skip.mjs +85 -86
  67. package/scripts/lib/session-end/tail-runner.mjs +178 -0
  68. package/scripts/lib/session-identity/own-session.mjs +24 -13
  69. package/scripts/lib/session-schema/constants.mjs +6 -0
  70. package/scripts/lib/session-schema/validator.mjs +20 -0
  71. package/scripts/lib/session-shape.mjs +558 -0
  72. package/scripts/lib/session-start-probes.mjs +10 -3
  73. package/scripts/lib/session-token-rollup.mjs +95 -10
  74. package/scripts/lib/state-md/frontmatter-mutators.mjs +22 -34
  75. package/scripts/lib/state-md.mjs +1 -0
  76. package/scripts/lib/subagents-schema.mjs +77 -9
  77. package/scripts/lib/telemetry/pricing.mjs +197 -0
  78. package/scripts/lib/telemetry/sync.mjs +50 -1
  79. package/scripts/lib/validate/check-owner-leakage.mjs +17 -8
  80. package/scripts/lib/validate/check-skill-script-paths.mjs +33 -10
  81. package/scripts/lib/validate/check-unwired-features.mjs +8 -7
  82. package/scripts/lib/vault-mirror/process.mjs +2 -1
  83. package/scripts/lib/vault-mirror/render-sessions.mjs +8 -1
  84. package/scripts/lib/vault-status/narrative-mirror.mjs +4 -4
  85. package/scripts/lib/wave-resource-gate.mjs +23 -27
  86. package/scripts/lib/wave-sizing.mjs +10 -3
  87. package/scripts/materialize-wave-scope.mjs +68 -14
  88. package/scripts/print-applicable-rules.mjs +7 -6
  89. package/scripts/print-learnings-index.mjs +3 -2
  90. package/scripts/release.mjs +32 -11
  91. package/scripts/session-shape.mjs +266 -0
  92. package/skills/_shared/config-reading.md +15 -9
  93. package/skills/_shared/private-capability-context.md +89 -0
  94. package/skills/bootstrap/SKILL.md +61 -13
  95. package/skills/bootstrap/_shared-template.md +99 -14
  96. package/skills/bootstrap/deep-template.md +36 -26
  97. package/skills/bootstrap/fast-template.md +44 -8
  98. package/skills/bootstrap/intensity-heuristic.md +10 -4
  99. package/skills/bootstrap/private-contract.md +119 -0
  100. package/skills/bootstrap/public-fallback.md +30 -18
  101. package/skills/bootstrap/standard-template.md +39 -24
  102. package/skills/discovery/probes-ui.md +1 -1
  103. package/skills/docs-orchestrator/audience-mapping.md +1 -1
  104. package/skills/evolve/SKILL.md +2 -2
  105. package/skills/gitlab-ops/SKILL.md +3 -3
  106. package/skills/grill/SKILL.md +1 -1
  107. package/skills/memory-cleanup/SKILL.md +2 -2
  108. package/skills/plan/mode-new.md +9 -0
  109. package/skills/reconcile/SKILL.md +1 -1
  110. package/skills/session-end/SKILL.md +3 -2
  111. package/skills/session-end/phase-3-2-docs-verification.md +1 -1
  112. package/skills/session-end/phase-3-6-tail.md +23 -65
  113. package/skills/session-end/phase-3-7a-recommendations.md +2 -2
  114. package/skills/session-end/references/phase-3-documentation-updates.md +8 -6
  115. package/skills/session-end/references/phase-5-issue-cleanup.md +26 -0
  116. package/skills/session-end/session-metrics-write.md +31 -12
  117. package/skills/session-plan/SKILL.md +56 -48
  118. package/skills/session-plan/wave-template.md +8 -15
  119. package/skills/session-start/SKILL.md +18 -2
  120. package/skills/session-start/phase-2-5-docs-planning.md +1 -1
  121. package/skills/session-start/phase-8-5-express-path.md +12 -9
  122. package/skills/session-start/references/phase-1-5-session-continuity.md +2 -0
  123. package/skills/session-start/references/phase-4-ssot-environment-check.md +21 -5
  124. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +3 -1
  125. package/skills/test-runner/rubric-v1.md +2 -2
  126. package/skills/wave-executor/SKILL.md +42 -12
  127. package/skills/wave-executor/circuit-breaker.md +3 -1
  128. package/skills/wave-executor/references/wave-loop-dispatch.md +4 -2
  129. package/skills/wave-executor/references/wave-loop-review.md +1 -1
  130. package/skills/wave-executor/references/wave-loop-scope-manifest.md +6 -2
  131. package/templates/nextjs-minimal/package.json +1 -1
  132. package/templates/node-minimal/package.json +1 -1
  133. package/scripts/lib/multi-provider-build/providers.mjs +0 -64
  134. package/scripts/lib/multi-provider-build/templating.mjs +0 -130
  135. package/scripts/lib/owner-config/coerce.mjs +0 -29
  136. package/scripts/lib/owner-config/constants.mjs +0 -21
  137. package/scripts/lib/owner-config/defaults.mjs +0 -50
  138. package/scripts/lib/owner-config/error.mjs +0 -19
  139. package/scripts/lib/owner-config/index.mjs +0 -13
  140. package/scripts/lib/owner-config/merge.mjs +0 -52
  141. package/scripts/lib/owner-config/validate.mjs +0 -259
  142. package/scripts/lib/owner-config-loader.mjs +0 -170
  143. package/scripts/lib/owner-config.mjs +0 -28
  144. package/scripts/lib/soul-resolve.mjs +0 -130
  145. package/scripts/lib/vault-mirror/render.mjs +0 -8
@@ -42,8 +42,9 @@ Store `INVOCATION_MODE = transitive | direct`.
42
42
 
43
43
  **Before dispatching to any tier template**, read `skills/bootstrap/public-fallback.md` and execute Step 1 (PATH_TYPE detection). Store the result as `PATH_TYPE = private | public`. This detection is silent — no user interaction.
44
44
 
45
- - `private`: `plan-baseline-path` is present in Session Config AND the path exists on disk. Baseline templates will be used for CLAUDE.md generation and archetype file sourcing.
46
- - `public`: `plan-baseline-path` is absent, empty, or points to a non-existent path. Plugin-bundled templates from `templates/` will be used.
45
+ - `private`: the existing host-local config resolution found a baseline directory and its reduced contract validated. Use `private-contract.md` for selection, templates, commands, CI and rules.
46
+ - `public`: the resolved baseline is absent, empty, or points to a missing directory. Use plugin-bundled templates.
47
+ - Existing but invalid configured baseline: abort before dispatch; report the reader's sanitized error reason.
47
48
 
48
49
  Pass `PATH_TYPE` into Phase 1 and all subsequent phases. All tier templates (`fast-template.md`, `standard-template.md`, `deep-template.md`) must consult `public-fallback.md` for CLAUDE.md generation and archetype file sourcing when `PATH_TYPE = public`.
49
50
 
@@ -59,7 +60,7 @@ Inputs to the heuristic:
59
60
 
60
61
  Output from Phase 1:
61
62
  - `RECOMMENDED_TIER` = `fast` | `standard` | `deep`
62
- - `RECOMMENDED_ARCHETYPE` = `static-html` | `node-minimal` | `nextjs-minimal` | `python-uv` | `null`
63
+ - `RECOMMENDED_ARCHETYPE` = validated private contract ID, public ID, or `null`
63
64
  - `HEURISTIC_REASON` = one-sentence explanation of why this tier was chosen (shown to user)
64
65
  - `PATH_TYPE` = `private` (plan-baseline-path configured and path exists) | `public` (no baseline)
65
66
 
@@ -120,7 +121,16 @@ AskUserQuestion({
120
121
  })
121
122
  ```
122
123
 
123
- Store as `CONFIRMED_ARCHETYPE`. The tier/stack block contributes **1–2** questions; a first-run full bootstrap adds **6 more** from the owner interview (Phase 3.5, five questions) and dispatcher-autonomy capture (Phase 3.5.1, one question) — **7–9 total**.
124
+ Store as `CONFIRMED_ARCHETYPE`.
125
+
126
+ For `PATH_TYPE = private` and Standard/Deep, execute `private-contract.md`'s
127
+ Select section now. Reuse a valid detected or explicit ID; when evidence is
128
+ insufficient, select from the returned catalog before scaffolding. Tier flags
129
+ skip tier confirmation, not required private archetype selection. Never pass a
130
+ null private ID into the public default. On upgrades, validate the lock's ID
131
+ against the currently configured contract before generating any files.
132
+
133
+ The tier/stack block contributes **1–2** questions; a first-run full bootstrap adds **6 more** from the owner interview (Phase 3.5, five questions) and dispatcher-autonomy capture (Phase 3.5.1, one question) — **7–9 total**.
124
134
 
125
135
  ## Upgrade Flow (`--upgrade <tier>`)
126
136
 
@@ -139,7 +149,12 @@ Entered when `$ARGUMENTS` contains `--upgrade <tier>`. No scaffolding questions
139
149
  `Error: Cannot downgrade from <CURRENT_TIER> to <TARGET_TIER>. Upgrade path is one-directional (fast → standard → deep).`
140
150
  Exit non-zero.
141
151
 
142
- 4. **Compute delta.** Determine which files the target tier adds over the current tier:
152
+ 4. **Resolve source and compute delta.** Run Phase 0.5's read-only source
153
+ detection before dispatching any template. For a private contract, validate
154
+ the lock's archetype with `--archetype`; if the Fast lock has no archetype,
155
+ select from the returned catalog using `private-contract.md`. Use its staged,
156
+ additive scaffold and CI expectations; do not apply the public file matrix.
157
+ For the public path, determine which files the target tier adds:
143
158
  - `fast → standard`: all Standard-tier files (`package.json`/`pyproject.toml`, `tsconfig.json`, `eslint.config.mjs`, `.prettierrc`, `.editorconfig`, `tests/`, `src/`)
144
159
  - `standard → deep`: all Deep-tier files (CI pipeline, `CODEOWNERS`, `CHANGELOG.md`, issue templates, MR/PR template, branch protection)
145
160
  - `fast → deep`: union of both deltas (apply Standard first, then Deep)
@@ -148,7 +163,7 @@ Entered when `$ARGUMENTS` contains `--upgrade <tier>`. No scaffolding questions
148
163
 
149
164
  6. **Apply delta files.** Execute only the relevant template steps for the missing files. Read the appropriate template (`standard-template.md` and/or `deep-template.md`) and execute ONLY the steps that produce the delta files. Do NOT re-run already-completed steps.
150
165
 
151
- 7. **Update bootstrap.lock atomically.** Overwrite `.orchestrator/bootstrap.lock` with `tier: <TARGET_TIER>`. Preserve `archetype`, `timestamp` (update to now), and `source` from the existing lock. Write `plugin-version` from `$PLUGIN_ROOT/package.json` (current plugin version at upgrade time).
166
+ 7. **Update bootstrap.lock atomically.** Overwrite `.orchestrator/bootstrap.lock` with `tier: <TARGET_TIER>`. Preserve a validated existing `archetype`; when upgrading a null Fast archetype, record the newly confirmed ID and scaffold source. Update `timestamp` to now. Preserve the prior `source` otherwise. Write `plugin-version` from `$PLUGIN_ROOT/package.json` (current plugin version at upgrade time).
152
167
 
153
168
  8. **Commit.** Stage only the delta files that were just written and commit:
154
169
  ```bash
@@ -185,7 +200,11 @@ Entered when `$ARGUMENTS` contains `--retroactive`. Writes the lock file and, pe
185
200
 
186
201
  Store as `INFERRED_TIER`.
187
202
 
188
- 4. **Infer archetype.** Best-effort detection from existing files:
203
+ 4. **Infer archetype.** Run Phase 0.5's read-only source detection. For a private
204
+ contract, use its detected `selected.id`; retain `null` with an explicit
205
+ `insufficient-evidence` report if no markers match. An invalid configured
206
+ contract aborts. Do not scaffold or apply rules in this retroactive flow.
207
+ For the public path, use best-effort detection from existing files:
189
208
  - `pyproject.toml` present → `python-uv`
190
209
  - `package.json` with `next` in dependencies → `nextjs-minimal`
191
210
  - `package.json` without `next` → `node-minimal`
@@ -296,7 +315,8 @@ Entered when `$ARGUMENTS` contains `--refresh-lock`. No scaffolding questions ar
296
315
 
297
316
  ## Sync-Rules Flow (`--sync-rules`)
298
317
 
299
- Entered when `$ARGUMENTS` contains `--sync-rules`. This is a standalone flow — it short-circuits the tier/archetype/scaffolding flow. No `bootstrap.lock` read, no template dispatched, no initial commit.
318
+ Entered when `$ARGUMENTS` contains `--sync-rules`. This standalone flow skips tier
319
+ selection, scaffolding and initial commit. Rule selection may read the lock ID.
300
320
 
301
321
  **Purpose:** Vendor canonical rules from the plugin's `rules/` library (`rules/always-on/*.md`, and in the future `rules/opt-in-stack/*.md` and `rules/opt-in-domain/*.md`) into the consumer repo's `.claude/rules/`. Plugin-sourced files (identified by a `<!-- source: session-orchestrator plugin … -->` header) are overwritten on re-run; files without that header are preserved as local overrides. See `rules/_index.md` for the canonical manifest and `scripts/lib/rules-sync.mjs` for the implementation.
302
322
 
@@ -304,13 +324,34 @@ Entered when `$ARGUMENTS` contains `--sync-rules`. This is a standalone flow —
304
324
 
305
325
  1. **Resolve plugin root.** The plugin's `rules/_index.md` lives next to `SKILL.md`'s plugin directory. Use the plugin root inferred by the harness (`PLUGIN_ROOT`).
306
326
 
307
- 2. **Invoke `scripts/lib/rules-sync.mjs`.** Run the CLI entrypoint from the consumer repo:
327
+ 2. **Invoke the bootstrap rule action.** It reloads a configured private contract
328
+ and supplies required plugin basenames to `scripts/lib/rules-sync.mjs`.
329
+ With no baseline, the writer's public/default behavior is unchanged. Map an
330
+ explicit `--archetype ID` to `CONFIRMED_ARCHETYPE`, `--dry-run` to
331
+ `DRY_RUN=true`, and optional category selections to comma-separated
332
+ `RULES_CATEGORIES`; otherwise leave those variables unset. Run from the repo:
308
333
 
309
334
  ```bash
310
- node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$(pwd)"
335
+ export PLUGIN_ROOT CONFIRMED_ARCHETYPE DRY_RUN RULES_CATEGORIES
336
+ node --input-type=module <<'NODE'
337
+ import { pathToFileURL } from 'node:url';
338
+ const { syncBootstrapRules } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
339
+ const categories = (process.env.RULES_CATEGORIES || '').split(',').map(value => value.trim()).filter(Boolean);
340
+ const result = await syncBootstrapRules({ repoRoot: process.cwd(), archetype: process.env.CONFIRMED_ARCHETYPE || undefined,
341
+ dryRun: process.env.DRY_RUN === 'true', categories: categories.length ? categories : null });
342
+ process.stdout.write(`${JSON.stringify(result)}\n`);
343
+ if (result.status === 'error') process.exitCode = 2;
344
+ NODE
311
345
  ```
312
346
 
313
- The script reads `rules/_index.md` from the plugin, iterates `always-on/` sources, and writes each file into `.claude/rules/` under the target repo. Stdout is a JSON object with `written[]`, `skipped[]`, `preserved[]`, and `errors[]`. Exit 1 on any error, 0 otherwise.
347
+ The canonical writer reads all selected categories in `rules/_index.md` and
348
+ writes into `.claude/rules/`. Required private targets remain subject to its
349
+ provenance and pre-write checks. Explicit ID takes precedence over lock ID,
350
+ then repository markers. Invalid private contracts abort before writes.
351
+ A valid Fast lock with `archetype: null` and no matching markers retains
352
+ ordinary plugin rule delivery after contract validation.
353
+ Stdout includes `status`, `created[]`, `written[]`, `skipped[]`, `preserved[]`,
354
+ and `errors[]`. Any error exits non-zero.
314
355
 
315
356
  Add `--dry-run` to preview without writing.
316
357
 
@@ -351,7 +392,7 @@ Pass the following context into the template execution:
351
392
  Follow the template's instructions precisely. The template is responsible for creating all files and the initial git commit.
352
393
 
353
394
  **Platform note for CLAUDE.md generation:**
354
- When `PATH_TYPE = public`, read `skills/bootstrap/public-fallback.md` for the full platform-specific CLAUDE.md generation logic (claude init path for Claude Code; `_minimal` template synthesis for Codex/Cursor). When `PATH_TYPE = private`, use the baseline scripts at `$BASELINE_PATH`.
395
+ When `PATH_TYPE = public`, read `skills/bootstrap/public-fallback.md` for the full platform-specific CLAUDE.md generation logic (claude init path for Claude Code; `_minimal` template synthesis for Codex/Cursor). When `PATH_TYPE = private`, use the validated, staged flow in `private-contract.md`.
355
396
 
356
397
  ## Phase 3.4: Vault-Registration Prompt (#190)
357
398
 
@@ -438,7 +479,14 @@ if (!isDispatcherAutonomyBlockPresent(content)) {
438
479
 
439
480
  > Closes session-orchestrator issue #110.
440
481
 
441
- After the tier template completes scaffolding (Phase 3), the Standard and Deep templates run an optional rules-fetch step that pulls canonical `.claude/rules/*.md` (and optionally `.claude/agents/*.md`) directly from the baseline GitLab project. The step is opt-in and only fires when:
482
+ After scaffolding, the Standard and Deep templates execute S99. On the private
483
+ path, it applies the selected contract's local rule union, rechecks conditional
484
+ dependencies and preserves existing files. It excludes every plugin-owned
485
+ basename and aborts on an invalid configured contract; see `private-contract.md`.
486
+
487
+ On the public path, S99 retains the optional remote rules-fetch step. It pulls
488
+ canonical `.claude/rules/*.md` directly from the configured baseline GitLab
489
+ project, excluding all plugin-owned basenames. The remote step only fires when:
442
490
 
443
491
  - `baseline-ref` is present in Session Config
444
492
  - `GITLAB_TOKEN` env var is set
@@ -24,21 +24,39 @@ Idempotency is handled by the writer itself:
24
24
  Shell:
25
25
  ```bash
26
26
  mkdir -p "$REPO_ROOT/.claude"
27
- node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"
28
- cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
27
+ export PLUGIN_ROOT REPO_ROOT CONFIRMED_ARCHETYPE
28
+ RULES_RESULT=$(node --input-type=module <<'NODE'
29
+ import { pathToFileURL } from 'node:url';
30
+ const { syncBootstrapRules } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
31
+ const result = await syncBootstrapRules({ repoRoot: process.env.REPO_ROOT, archetype: process.env.CONFIRMED_ARCHETYPE || undefined });
32
+ process.stdout.write(`${JSON.stringify(result)}\n`);
33
+ if (result.status === 'error') process.exitCode = 2;
34
+ NODE
35
+ ) || exit 2
36
+ printf '%s\n' "$RULES_RESULT"
37
+ while IFS= read -r _file; do BOOTSTRAP_FILES+=("$_file"); done \
38
+ < <(printf '%s\n' "$RULES_RESULT" | jq -r '.created[]')
39
+ if [[ ! -e "$REPO_ROOT/.claude/loop.md" && ! -L "$REPO_ROOT/.claude/loop.md" ]]; then
40
+ cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
41
+ BOOTSTRAP_FILES+=(.claude/loop.md)
42
+ fi
29
43
  ```
30
44
 
31
45
  The command prints a JSON report (`written` / `skipped` / `preserved` / `errors` / `warnings` / `sanitizer`) and exits non-zero on any error. Surface `errors[]` to the operator; a non-empty `preserved[]` is normal and means a repo-private rule was left alone.
32
46
 
33
47
  Also surface `sanitizer[]` (issue #1098) — `{file, line, kind, text}` records for citations that read fine inside the plugin repo and dangle once vendored (`repo-local-path`, `unresolvable-see-also`). The CLI additionally prints each one to stderr as `rules-sync: sanitizer <kind> <file>:<line> — <text>`. **Report it to the operator; do not act on it automatically** — the sanitizer never rewrites content and never changes the exit code, because silently stripping a citation would change a rule's meaning at vendoring time. A human decides whether the citation is a leak.
34
48
 
35
- Archetype-scoped entries in `rules/_index.md` resolve from `.orchestrator/bootstrap.lock`, which does not exist yet at this step — they report `archetype-unknown` and are skipped. The always-on rules (including `parallel-sessions.md`) are universal and vendor regardless. Re-run `/bootstrap --sync-rules` after the lock is written to pick up the archetype-scoped ones.
49
+ The wrapper re-reads a configured private contract and passes its required plugin
50
+ basenames into the canonical writer. The full set must resolve uniquely before
51
+ any rule is written; source/provenance validation and local preservation still
52
+ apply. On the public path, normal archetype filtering is unchanged. The selected
53
+ ID is explicit during bootstrap; later `/bootstrap --sync-rules` can use the lock.
36
54
 
37
55
  Why: PSA-003 destructive-command safeguards require every consumer repo to carry the parallel-sessions rule. See issue #155. The `loop.md` vendor gives bare `/loop` a repo-aware maintenance prompt (issue #633 Hebel 3).
38
56
 
39
- Why one writer (issue #1060): a literal `cp` from a second source directory bypasses the pre-write validator AND lands a file carrying no provenance header. On the next `--sync-rules` a headerless file is classified as a repo-private override and preserved forever — so the plugin can never update it again, and whichever rival copy is smaller silently wins. `rules/` is the only source with a manifest, archetype scoping, a basename-collision guard and a pre-write validator, so it is the only sanctioned writer to `.claude/rules/`.
57
+ Why one writer (issue #1060): a literal `cp` from a second source directory bypasses the pre-write validator AND lands a file carrying no provenance header. On the next `--sync-rules` a headerless file is classified as a repo-private override and preserved forever — so the plugin can never update it again, and whichever rival copy is smaller silently wins. For every basename declared in `rules/_index.md`, `rules-sync.mjs` is the sole writer: it owns the manifest, archetype scoping, basename-collision guard and pre-write validation. S99 may deliver baseline-only rules after excluding all plugin-owned basenames.
40
58
 
41
- Note: This step runs before the baseline-fetch step (S99/D99), and S99 must NOT overwrite a rule that `rules/` owns. The baseline's copy carries no provenance header, so letting it win would permanently mark the target as a repo-private override — the exact failure described above. `.claude/rules/parallel-sessions.md` has therefore been removed from the S99 manifest. Any other basename present in BOTH `rules/_index.md` and the S99 manifest has the same defect and needs the same treatment.
59
+ Note: This step runs before S99/D99. Both the private local rule projection and the optional public fetch filter every basename in `rules/_index.md`, including currently nonmatching scoped entries. `parallel-sessions.md` and any future plugin-owned rule therefore remain under the same single writer.
42
60
 
43
61
  ---
44
62
 
@@ -48,7 +66,13 @@ Copy the opinionated agent templates into the consumer repo:
48
66
 
49
67
  ```bash
50
68
  mkdir -p "$REPO_ROOT/.claude/agents"
51
- cp "$PLUGIN_ROOT/skills/bootstrap/templates/agents/"*.md "$REPO_ROOT/.claude/agents/"
69
+ for _source in "$PLUGIN_ROOT/skills/bootstrap/templates/agents/"*.md; do
70
+ _target=".claude/agents/$(basename "$_source")"
71
+ if [[ ! -e "$REPO_ROOT/$_target" && ! -L "$REPO_ROOT/$_target" ]]; then
72
+ cp "$_source" "$REPO_ROOT/$_target"
73
+ BOOTSTRAP_FILES+=("$_target")
74
+ fi
75
+ done
52
76
  ```
53
77
 
54
78
  This scaffolds 3 opinionated agents (`project-discovery`, `project-code-review`, `project-quality-gate`) following CLAUDE.md Agent Authoring Rules. Consumer repos should edit descriptions/bodies to match project specifics — but keep the frontmatter structure intact (validated by `agent-frontmatter-invalid` probe).
@@ -103,7 +127,13 @@ vault:
103
127
 
104
128
  ## #baseline-fetch — Step S99: (Optional) Fetch Canonical Rules + Agents from Baseline
105
129
 
106
- This step is OPT-IN and only executes when ALL of the following are true:
130
+ For `PATH_TYPE = private` and a confirmed archetype, this step applies only the
131
+ validated local contract rule targets from `private-contract.md`. It is offline,
132
+ rechecks conditional dependencies after scaffolding, preserves existing files,
133
+ and fails closed on an invalid configured contract.
134
+
135
+ For `PATH_TYPE = public`, the existing remote fetch remains OPT-IN and only
136
+ executes when ALL of the following are true:
107
137
  - `baseline-ref` is present in Session Config (e.g., `baseline-ref: main`)
108
138
  - `GITLAB_TOKEN` env var is set
109
139
  - The session-orchestrator plugin includes `scripts/lib/fetch-baseline.mjs`
@@ -116,6 +146,23 @@ Without this step, rules arrive in the repo via Clank's weekly baseline sync MRs
116
146
  **Implementation:**
117
147
 
118
148
  ```bash
149
+ if [[ "${PATH_TYPE:-public}" = "private" ]]; then
150
+ export PLUGIN_ROOT REPO_ROOT CONFIRMED_ARCHETYPE
151
+ BASELINE_RULES_RESULT=$(node --input-type=module <<'NODE'
152
+ import { pathToFileURL } from 'node:url';
153
+ const { applyBaselineRules } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
154
+ const result = await applyBaselineRules({
155
+ repoRoot: process.env.REPO_ROOT,
156
+ archetype: process.env.CONFIRMED_ARCHETYPE,
157
+ });
158
+ process.stdout.write(`${JSON.stringify(result)}\n`);
159
+ if (result.status === 'error') process.exitCode = 2;
160
+ NODE
161
+ ) || exit 2
162
+ printf '%s\n' "$BASELINE_RULES_RESULT"
163
+ while IFS= read -r _file; do BOOTSTRAP_FILES+=("$_file"); done \
164
+ < <(printf '%s\n' "$BASELINE_RULES_RESULT" | jq -r '.created[]')
165
+ else
119
166
  BASELINE_REF=$(echo "$CONFIG" | jq -r '."baseline-ref" // empty')
120
167
  BASELINE_PROJECT_ID=$(echo "$CONFIG" | jq -r '."baseline-project-id" // "52"')
121
168
 
@@ -145,6 +192,19 @@ if [[ -n "$BASELINE_REF" && -n "${GITLAB_TOKEN:-}" && -n "${GITLAB_HOST:-}" && -
145
192
  .claude/rules/ai-agent.md
146
193
  .claude/rules/claude-code-usage.md
147
194
  MANIFEST
195
+ # Every plugin-owned basename is excluded, including currently unmatched scoped
196
+ # rules. rules-sync is their sole writer; ownership does not depend on scope.
197
+ export PLUGIN_ROOT RULES_MANIFEST
198
+ node --input-type=module <<'NODE'
199
+ import { readFileSync, writeFileSync } from 'node:fs';
200
+ import path from 'node:path';
201
+ import { pathToFileURL } from 'node:url';
202
+ const { pluginRuleTargets } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
203
+ const owned = new Set(pluginRuleTargets(process.env.PLUGIN_ROOT));
204
+ const files = readFileSync(process.env.RULES_MANIFEST, 'utf8').split('\n')
205
+ .filter(file => file && !owned.has(path.posix.basename(file)));
206
+ writeFileSync(process.env.RULES_MANIFEST, files.join('\n') + '\n');
207
+ NODE
148
208
 
149
209
  echo "Fetching canonical rules from baseline (project $BASELINE_PROJECT_ID, ref $BASELINE_REF)…"
150
210
  # The .mjs CLI is single-file: it prints ONE file body to stdout, exit 0 on success
@@ -153,10 +213,13 @@ MANIFEST
153
213
  SUCCESS_LOG=$(mktemp)
154
214
  while IFS= read -r rule_path; do
155
215
  [[ -z "$rule_path" ]] && continue
216
+ _RULE_CREATED=false
217
+ [[ -e "$REPO_ROOT/$rule_path" || -L "$REPO_ROOT/$rule_path" ]] || _RULE_CREATED=true
156
218
  mkdir -p "$REPO_ROOT/$(dirname "$rule_path")"
157
219
  if node "$PLUGIN_ROOT/scripts/lib/fetch-baseline.mjs" \
158
220
  "$BASELINE_PROJECT_ID" "$rule_path" "$BASELINE_REF" > "$REPO_ROOT/$rule_path"; then
159
221
  printf '%s\n' "$rule_path" >> "$SUCCESS_LOG"
222
+ if [[ "$_RULE_CREATED" = true ]]; then BOOTSTRAP_FILES+=("$rule_path"); fi
160
223
  else
161
224
  # A 404 (or any error) for one rule must not abort the batch — drop the empty
162
225
  # target the redirect created and continue with the next manifest line.
@@ -167,6 +230,8 @@ MANIFEST
167
230
  if [[ -s "$SUCCESS_LOG" ]]; then
168
231
  FETCHED_JSON=$(jq -R . < "$SUCCESS_LOG" | jq -s .)
169
232
  LOCK_FILE="$REPO_ROOT/.claude/.baseline-fetch.lock"
233
+ _FETCH_LOCK_CREATED=false
234
+ [[ -e "$LOCK_FILE" || -L "$LOCK_FILE" ]] || _FETCH_LOCK_CREATED=true
170
235
  mkdir -p "$REPO_ROOT/.claude"
171
236
  FETCHED_JSON="$FETCHED_JSON" BASELINE_PROJECT_ID="$BASELINE_PROJECT_ID" \
172
237
  BASELINE_REF="$BASELINE_REF" LOCK_FILE="$LOCK_FILE" \
@@ -183,6 +248,7 @@ MANIFEST
183
248
  writeFileSync(process.env.LOCK_FILE, JSON.stringify(lock, null, 2) + '\n');
184
249
  "
185
250
  echo "Wrote .claude/.baseline-fetch.lock ($(wc -l < "$SUCCESS_LOG" | tr -d ' ') files)"
251
+ if [[ "$_FETCH_LOCK_CREATED" = true ]]; then BOOTSTRAP_FILES+=(.claude/.baseline-fetch.lock); fi
186
252
  else
187
253
  echo "WARNING: baseline fetch produced no files; rules will arrive via Clank sync MRs (legacy path)" >&2
188
254
  fi
@@ -191,23 +257,39 @@ MANIFEST
191
257
  else
192
258
  echo "Skipping baseline fetch: baseline-ref / GITLAB_TOKEN / GITLAB_HOST not configured (legacy Clank-sync path)"
193
259
  fi
260
+ fi
194
261
  ```
195
262
 
196
- **Failure handling:** If the fetch fails, this step DOES NOT abort bootstrap. The repo still has its scaffold; rules will arrive via the legacy Clank weekly sync MR. The user is informed via stderr.
263
+ **Failure handling:** A private contract/apply error aborts bootstrap. For the public optional remote path, if the fetch fails, this step DOES NOT abort bootstrap. The repo still has its scaffold; rules will arrive via the legacy Clank weekly sync MR. The user is informed via stderr.
197
264
 
198
- **Idempotency:** Re-running bootstrap on an existing repo will overwrite `.claude/rules/*.md` files. Local edits to baseline rules in a repo will be lost on re-fetch — this is intentional (rules are canonical). Repo-specific extensions belong in `.claude/rules/local/*.md` (not fetched).
265
+ **Idempotency:** Private local rules preserve existing files and report them for review. On the public optional remote path, re-running bootstrap on an existing repo will overwrite `.claude/rules/*.md` files. Local edits to baseline rules in a repo will be lost on re-fetch — this is intentional (rules are canonical). Repo-specific extensions belong in `.claude/rules/local/*.md` (not fetched).
199
266
 
200
267
  ---
201
268
 
202
269
  ## #quality-gate-policy — Step 6.5: Quality-Gate Policy File (#183)
203
270
 
204
- Write the canonical quality-gate commands to `.orchestrator/policy/quality-gates.json`. Bootstrap detects the package manager and writes sensible defaults; users may hand-edit afterwards.
271
+ Write canonical commands to `.orchestrator/policy/quality-gates.json`. A private
272
+ contract supplies exact test/typecheck/lint IDs, with `false` and an unavailable
273
+ reason for absent IDs. Public bootstrap retains package-manager defaults.
205
274
 
206
275
  **Idempotency:** Skip this step if `.orchestrator/policy/quality-gates.json` already exists. Do not overwrite user edits.
207
276
 
208
277
  ```bash
209
278
  POLICY_FILE="$REPO_ROOT/.orchestrator/policy/quality-gates.json"
210
- if [[ ! -f "$POLICY_FILE" ]]; then
279
+ if [[ "${PATH_TYPE:-public}" = private ]]; then
280
+ export PLUGIN_ROOT REPO_ROOT CONFIRMED_ARCHETYPE
281
+ POLICY_RESULT=$(node --input-type=module <<'NODE'
282
+ import { pathToFileURL } from 'node:url';
283
+ const { writeBaselineQualityPolicy } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
284
+ const result = await writeBaselineQualityPolicy({ repoRoot: process.env.REPO_ROOT, archetype: process.env.CONFIRMED_ARCHETYPE });
285
+ process.stdout.write(`${JSON.stringify(result)}\n`);
286
+ if (result.status === 'error') process.exitCode = 2;
287
+ NODE
288
+ ) || exit 2
289
+ printf '%s\n' "$POLICY_RESULT"
290
+ while IFS= read -r _file; do BOOTSTRAP_FILES+=("$_file"); done \
291
+ < <(printf '%s\n' "$POLICY_RESULT" | jq -r '.created[]')
292
+ elif [[ ! -e "$POLICY_FILE" && ! -L "$POLICY_FILE" ]]; then
211
293
  mkdir -p "$REPO_ROOT/.orchestrator/policy"
212
294
  # Detect package manager via scripts/lib/package-manager.mjs (falls back to npm defaults)
213
295
  PM_JSON="$(node --input-type=module -e "
@@ -226,6 +308,7 @@ if [[ ! -f "$POLICY_FILE" ]]; then
226
308
  "rationale": "Canonical quality-gate commands. Generated by bootstrap. Edit to change test/typecheck/lint invocations across skills. Schema: .orchestrator/policy/quality-gates.schema.json",
227
309
  "commands": $cmds
228
310
  }' > "$POLICY_FILE"
311
+ BOOTSTRAP_FILES+=(.orchestrator/policy/quality-gates.json)
229
312
  echo "Wrote $POLICY_FILE"
230
313
  fi
231
314
  ```
@@ -234,16 +317,18 @@ fi
234
317
 
235
318
  ## #state-md-scaffold — Step 6.6: STATE.md Scaffold (#184)
236
319
 
237
- Scaffold a placeholder `.claude/STATE.md` using the template at `skills/bootstrap/STATE.md.template`. The placeholder records `status: idle` — sessions overwrite it at Pre-Wave 1b.
320
+ Scaffold a placeholder `.claude/STATE.md`. <!-- path-check: example -->
321
+ Use the template at `skills/bootstrap/STATE.md.template`; the placeholder records `status: idle` — sessions overwrite it at Pre-Wave 1b.
238
322
 
239
- **Idempotency:** Skip if `.claude/STATE.md` already exists.
323
+ **Idempotency:** Skip if `.claude/STATE.md` already exists. <!-- path-check: example -->
240
324
 
241
325
  ```bash
242
326
  STATE_FILE="$REPO_ROOT/.claude/STATE.md"
243
- if [[ ! -f "$STATE_FILE" ]]; then
327
+ if [[ ! -e "$STATE_FILE" && ! -L "$STATE_FILE" ]]; then
244
328
  mkdir -p "$REPO_ROOT/.claude"
245
329
  ISO_NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
246
330
  sed "s|<ISO>|$ISO_NOW|g" "$PLUGIN_ROOT/skills/bootstrap/STATE.md.template" > "$STATE_FILE"
331
+ BOOTSTRAP_FILES+=(.claude/STATE.md)
247
332
  echo "Wrote $STATE_FILE"
248
333
  fi
249
334
  ```
@@ -5,6 +5,9 @@
5
5
 
6
6
  Deep tier is a strict superset of Standard tier. Execute all Standard-tier steps first, then the Deep-specific steps below.
7
7
 
8
+ Keep the inherited `BOOTSTRAP_FILES` array. Append each new governance file by
9
+ its relative filename when created; do not append existing files or directories.
10
+
8
11
  ## Step 1–8: Execute Standard Tier
9
12
 
10
13
  Read and execute `skills/bootstrap/standard-template.md` Steps 1–8 in full. Do not skip any step.
@@ -30,7 +33,13 @@ If `VCS` is empty or `none`, skip CI pipeline, issue templates, MR/PR template,
30
33
 
31
34
  ## Step D1: CI Pipeline
32
35
 
33
- Create the CI pipeline file. Mutually exclusive — create exactly one based on `VCS`.
36
+ When `PATH_TYPE = private`, keep the selected baseline's rendered CI and
37
+ `ci.profile` from `private-contract.md`. Respect `ci.required: false`; do not
38
+ create a generic Node pipeline for an exempt archetype. A required CI missing
39
+ from staging already aborts. Resolve VCS mismatch explicitly before changing
40
+ canonical CI. Skip the public D1 examples below and continue at D2.
41
+
42
+ For `PATH_TYPE = public`, create exactly one pipeline based on `VCS`.
34
43
 
35
44
  ### If `VCS = gitlab`: `.gitlab-ci.yml`
36
45
 
@@ -208,7 +217,7 @@ Replace `<YYYY-MM-DD>` with today's date in ISO 8601 format (e.g., `2026-04-16`)
208
217
  mkdir -p "$REPO_ROOT/.gitlab/issue_templates"
209
218
  ```
210
219
 
211
- Write `.gitlab/issue_templates/Bug.md`:
220
+ In the consuming repository, create `.gitlab/issue_templates/Bug.md`: <!-- path-check: example -->
212
221
 
213
222
  ```markdown
214
223
  ## Summary
@@ -239,7 +248,7 @@ Write `.gitlab/issue_templates/Bug.md`:
239
248
  <!-- Screenshots, logs, related issues -->
240
249
  ```
241
250
 
242
- Write `.gitlab/issue_templates/Feature.md`:
251
+ In the consuming repository, create `.gitlab/issue_templates/Feature.md`: <!-- path-check: example -->
243
252
 
244
253
  ```markdown
245
254
  ## Summary
@@ -334,7 +343,7 @@ labels: enhancement
334
343
  mkdir -p "$REPO_ROOT/.gitlab/merge_request_templates"
335
344
  ```
336
345
 
337
- Write `.gitlab/merge_request_templates/Default.md`:
346
+ In the consuming repository, create `.gitlab/merge_request_templates/Default.md`: <!-- path-check: example -->
338
347
 
339
348
  ```markdown
340
349
  ## Summary
@@ -492,12 +501,11 @@ If the call fails, log the structured message above and continue. Raw API respon
492
501
 
493
502
  Canonical implementation in [`_shared-template.md#parallel-sessions-rule`](_shared-template.md).
494
503
 
495
- Run `node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"` — it vendors every
496
- rule registered in `rules/_index.md` (idempotent: missing→create, identical→skip, differs→overwrite),
497
- and it is the only writer that applies the pre-write validator, the basename-collision guard and the
498
- copy-on-write branch that preserves repo-private rules. The shared partial also runs
499
- `cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"` so bare `/loop` gets a
500
- repo-aware maintenance prompt. See shared partial for full shell command. Issues #155, #633, #1060.
504
+ Execute the shared partial's `syncBootstrapRules` action. It supplies private
505
+ required basenames to `scripts/lib/rules-sync.mjs`, preserving its manifest,
506
+ pre-write validation and local-override checks. The partial appends actual newly
507
+ created rule paths, and creates `.claude/loop.md` only when missing. See the
508
+ shared partial for the executable shell command. Issues #155, #633, #1060.
501
509
 
502
510
  Note: Runs before D99. D99 (via inherited S99) no longer fetches `parallel-sessions.md` from the
503
511
  baseline — that entry was removed from the S99 manifest in #1060. The baseline copy carries no
@@ -511,11 +519,13 @@ Deep tier creates both learnings and sessions metrics plus a schema-linking READ
511
519
 
512
520
  ```bash
513
521
  mkdir -p "$REPO_ROOT/.orchestrator/metrics"
514
- [[ -f "$REPO_ROOT/.orchestrator/metrics/learnings.jsonl" ]] || \
515
- : > "$REPO_ROOT/.orchestrator/metrics/learnings.jsonl"
516
- [[ -f "$REPO_ROOT/.orchestrator/metrics/sessions.jsonl" ]] || \
517
- : > "$REPO_ROOT/.orchestrator/metrics/sessions.jsonl"
518
-
522
+ for _file in learnings.jsonl sessions.jsonl; do
523
+ if [[ ! -e "$REPO_ROOT/.orchestrator/metrics/$_file" && ! -L "$REPO_ROOT/.orchestrator/metrics/$_file" ]]; then
524
+ : > "$REPO_ROOT/.orchestrator/metrics/$_file"
525
+ BOOTSTRAP_FILES+=(".orchestrator/metrics/$_file")
526
+ fi
527
+ done
528
+ if [[ ! -e "$REPO_ROOT/.orchestrator/metrics/README.md" && ! -L "$REPO_ROOT/.orchestrator/metrics/README.md" ]]; then
519
529
  cat > "$REPO_ROOT/.orchestrator/metrics/README.md" <<'README'
520
530
  # Metrics
521
531
 
@@ -530,6 +540,8 @@ Schema: https://github.com/Kanevry/session-orchestrator
530
540
  **Do NOT gitignore** — these files are project artifacts intended to persist
531
541
  across sessions and contributors.
532
542
  README
543
+ BOOTSTRAP_FILES+=(.orchestrator/metrics/README.md)
544
+ fi
533
545
  ```
534
546
 
535
547
  **Idempotent.** Existing files are preserved.
@@ -547,8 +559,10 @@ Standard-template Step S99 already executed as part of "Step 1–8: Execute Stan
547
559
  No additional fetch action is needed here.
548
560
 
549
561
  If S99 ran successfully, `.claude/rules/*.md` and `.claude/.baseline-fetch.lock` are already written
550
- to `$REPO_ROOT`. These files are included in the Deep commit at Step D8 via the `.claude/` entry
551
- in `BOOTSTRAP_FILES`. If S99 was skipped, rules arrive via the legacy Clank weekly sync MR path.
562
+ to `$REPO_ROOT`. Their individual newly created paths remain in `BOOTSTRAP_FILES`
563
+ for the Deep commit at Step D8. If the private contract applies, S99 already
564
+ delivered its local filtered rule union. If public S99 was skipped, rules arrive
565
+ via the legacy Clank weekly sync MR path.
552
566
 
553
567
  ---
554
568
 
@@ -580,6 +594,8 @@ Write `.orchestrator/bootstrap.lock` **atomically** (mktemp + mv prevents a corr
580
594
  process is interrupted mid-write):
581
595
 
582
596
  ```bash
597
+ _LOCK_CREATED=false
598
+ [[ -e "$REPO_ROOT/.orchestrator/bootstrap.lock" || -L "$REPO_ROOT/.orchestrator/bootstrap.lock" ]] || _LOCK_CREATED=true
583
599
  _LOCK_TMP=$(mktemp "$REPO_ROOT/.orchestrator/bootstrap.lock.XXXXXX")
584
600
  cat > "$_LOCK_TMP" << LOCK
585
601
  # .orchestrator/bootstrap.lock
@@ -592,6 +608,7 @@ plugin-version: <session-orchestrator plugin version — read from $PLUGIN_ROOT/
592
608
  bootstrapped-at: <current ISO 8601 UTC — same value as timestamp; distinct field for age-validation probe>
593
609
  LOCK
594
610
  mv "$_LOCK_TMP" "$REPO_ROOT/.orchestrator/bootstrap.lock"
611
+ if [[ "$_LOCK_CREATED" = true ]]; then BOOTSTRAP_FILES+=(.orchestrator/bootstrap.lock); fi
595
612
  ```
596
613
 
597
614
  Set `source` using the same logic as fast-template Step 5.
@@ -604,16 +621,9 @@ Stage all created files and commit:
604
621
 
605
622
  ```bash
606
623
  cd "$REPO_ROOT"
607
- BOOTSTRAP_FILES=(
608
- CLAUDE.md AGENTS.md .gitignore README.md .orchestrator/bootstrap.lock
609
- .orchestrator/policy/quality-gates.json
610
- package.json pyproject.toml tsconfig.json eslint.config.mjs .prettierrc
611
- .editorconfig src/ tests/ CHANGELOG.md CODEOWNERS
612
- .gitlab/ .github/ .claude/
613
- )
614
624
  # Add only the files bootstrap created — no sweeping -u/-A to avoid catching pre-existing files
615
- for _f in "${BOOTSTRAP_FILES[@]}"; do
616
- [[ -e "$_f" ]] && git add -- "$_f"
625
+ for _f in ${BOOTSTRAP_FILES[@]+"${BOOTSTRAP_FILES[@]}"}; do
626
+ [[ -f "$_f" && ! -L "$_f" ]] && git add -- "$_f"
617
627
  done
618
628
  git commit -m "chore: bootstrap (deep)"
619
629
  ```
@@ -14,6 +14,10 @@
14
14
 
15
15
  Intentionally absent: `package.json`, frameworks, tests, CI config. The feature that follows brings its own stack.
16
16
 
17
+ Maintain the inherited `BOOTSTRAP_FILES` array and append every newly created
18
+ relative file at creation. Preserve existing owner files and never reset this
19
+ array when Fast is inherited by Standard or Deep.
20
+
17
21
  ## Step 1: Ensure Git Repo is Initialized
18
22
 
19
23
  ```bash
@@ -38,7 +42,12 @@ After `public-fallback.md` completes CLAUDE.md generation, continue to Step 2b t
38
42
 
39
43
  **If `PATH_TYPE = private`:**
40
44
 
41
- Use the baseline scripts at `$BASELINE_PATH` as directed by the baseline's own documentation. Proceed with baseline-driven CLAUDE.md generation, then continue to Step 2b.
45
+ For Standard/Deep inheritance, `private-contract.md` already rendered the
46
+ instruction file and stack into the repo. Preserve those files and continue
47
+ to Step 2b; use its quality-gate command mapping for missing Session Config
48
+ fields. Preserve the rendered README and `.gitignore` in subsequent Fast steps.
49
+ For a standalone Fast tier, no archetype is selected: use `public-fallback.md`'s
50
+ minimal Fast instruction-file generation and report `source: plugin-template`.
42
51
 
43
52
  **Step 2b: Verify Session Config block.** After writing or updating CLAUDE.md, check for the sentinel string `## Session Config`:
44
53
 
@@ -107,7 +116,8 @@ elif ls *.ts *.js package.json 2>/dev/null | head -1 | grep -q .; then STACK="no
107
116
  else STACK="generic"; fi
108
117
  ```
109
118
 
110
- Write `.gitignore` with the appropriate content:
119
+ If absent, write `.gitignore` with the appropriate content and append `.gitignore`
120
+ to `BOOTSTRAP_FILES`. Preserve an existing file.
111
121
 
112
122
  **Generic (no stack detected):**
113
123
  ```gitignore
@@ -166,6 +176,10 @@ Note: `.orchestrator/` is NOT gitignored — `bootstrap.lock` must be committed.
166
176
 
167
177
  Vendor the canonical always-on rules from the plugin's `rules/` library into `$REPO_ROOT/.claude/rules/`. `rules/` is the single source of truth for every distributable rule — never `cp` a rule file from anywhere else.
168
178
 
179
+ `syncBootstrapRules` calls the canonical `scripts/lib/rules-sync.mjs` writer.
180
+ Standalone Fast uses ordinary plugin rules without private archetype selection;
181
+ inherited Fast passes the confirmed contract ID and records all created paths.
182
+
169
183
  Idempotency is handled by the writer itself:
170
184
  - Missing → create
171
185
  - Exists, plugin-owned (first line is the `<!-- source: session-orchestrator plugin ... -->` header) and byte-identical → skip silently
@@ -175,11 +189,28 @@ Idempotency is handled by the writer itself:
175
189
  Shell:
176
190
  ```bash
177
191
  mkdir -p "$REPO_ROOT/.claude"
178
- node "$PLUGIN_ROOT/scripts/lib/rules-sync.mjs" --repo-root "$REPO_ROOT"
179
- cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
192
+ export PLUGIN_ROOT REPO_ROOT CONFIRMED_ARCHETYPE
193
+ RULES_RESULT=$(node --input-type=module <<'NODE'
194
+ import { pathToFileURL } from 'node:url';
195
+ const { syncBootstrapRules } = await import(pathToFileURL(`${process.env.PLUGIN_ROOT}/scripts/lib/baseline-archetypes.mjs`));
196
+ const result = await syncBootstrapRules({ repoRoot: process.env.REPO_ROOT,
197
+ archetype: process.env.CONFIRMED_ARCHETYPE || undefined, minimal: !process.env.CONFIRMED_ARCHETYPE });
198
+ process.stdout.write(`${JSON.stringify(result)}\n`);
199
+ if (result.status === 'error') process.exitCode = 2;
200
+ NODE
201
+ ) || exit 2
202
+ printf '%s\n' "$RULES_RESULT"
203
+ while IFS= read -r _file; do BOOTSTRAP_FILES+=("$_file"); done \
204
+ < <(printf '%s\n' "$RULES_RESULT" | jq -r '.created[]')
205
+ if [[ ! -e "$REPO_ROOT/.claude/loop.md" && ! -L "$REPO_ROOT/.claude/loop.md" ]]; then
206
+ cp "$PLUGIN_ROOT/templates/_shared/loop.md" "$REPO_ROOT/.claude/loop.md"
207
+ BOOTSTRAP_FILES+=(.claude/loop.md)
208
+ fi
180
209
  ```
181
210
 
182
- The command prints a JSON report (`written` / `skipped` / `preserved` / `errors` / `warnings` / `sanitizer`) and exits non-zero on any error. At fast tier `.orchestrator/bootstrap.lock` does not exist yet (Step 5 writes it), so archetype-scoped entries report `archetype-unknown` and are skipped — the always-on rules vendor regardless.
211
+ The command prints a JSON report and exits non-zero on any error. At standalone
212
+ Fast tier the lock and selected ID are absent, so scoped rules are skipped.
213
+ Inherited Fast delivers private required targets through the same writer.
183
214
 
184
215
  Surface `errors[]` and `sanitizer[]` to the operator. `sanitizer[]` (issue #1098) carries `{file, line, kind, text}` records for citations that read fine inside the plugin repo and dangle once vendored (`repo-local-path`, `unresolvable-see-also`); the CLI also prints each to stderr as `rules-sync: sanitizer <kind> <file>:<line> — <text>`. **Report it, do not act on it automatically** — it never rewrites content and never changes the exit code, so a human decides whether the citation is a leak.
185
216
 
@@ -189,6 +220,9 @@ Why one writer (issue #1060): a literal `cp` from a second source directory bypa
189
220
 
190
221
  ## Step 4: Generate README.md
191
222
 
223
+ If absent, write the stub below and append `README.md` to `BOOTSTRAP_FILES`.
224
+ Preserve an existing README.
225
+
192
226
  ```markdown
193
227
  # <REPO_NAME>
194
228
 
@@ -207,6 +241,8 @@ Write `.orchestrator/bootstrap.lock` **atomically** (mktemp + mv prevents a corr
207
241
  process is interrupted mid-write):
208
242
 
209
243
  ```bash
244
+ _LOCK_CREATED=false
245
+ [[ -e "$REPO_ROOT/.orchestrator/bootstrap.lock" || -L "$REPO_ROOT/.orchestrator/bootstrap.lock" ]] || _LOCK_CREATED=true
210
246
  _LOCK_TMP=$(mktemp "$REPO_ROOT/.orchestrator/bootstrap.lock.XXXXXX")
211
247
  cat > "$_LOCK_TMP" << LOCK
212
248
  # .orchestrator/bootstrap.lock
@@ -219,6 +255,7 @@ plugin-version: <session-orchestrator plugin version — read from $PLUGIN_ROOT/
219
255
  bootstrapped-at: <current ISO 8601 UTC — same value as timestamp; distinct field for age-validation probe>
220
256
  LOCK
221
257
  mv "$_LOCK_TMP" "$REPO_ROOT/.orchestrator/bootstrap.lock"
258
+ if [[ "$_LOCK_CREATED" = true ]]; then BOOTSTRAP_FILES+=(.orchestrator/bootstrap.lock); fi
222
259
  ```
223
260
 
224
261
  Set `source`:
@@ -231,10 +268,9 @@ Stage all created files and commit:
231
268
 
232
269
  ```bash
233
270
  cd "$REPO_ROOT"
234
- BOOTSTRAP_FILES=(CLAUDE.md AGENTS.md .gitignore README.md .orchestrator/bootstrap.lock .claude/rules/parallel-sessions.md)
235
271
  # Add only the files bootstrap created — no sweeping -u/-A to avoid catching pre-existing files
236
- for _f in "${BOOTSTRAP_FILES[@]}"; do
237
- [[ -e "$_f" ]] && git add -- "$_f"
272
+ for _f in ${BOOTSTRAP_FILES[@]+"${BOOTSTRAP_FILES[@]}"}; do
273
+ [[ -f "$_f" && ! -L "$_f" ]] && git add -- "$_f"
238
274
  done
239
275
  git commit -m "chore: bootstrap (fast)"
240
276
  ```