session-orchestrator 3.20.0 → 3.22.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor/rules/000-session-orchestrator.mdc +3 -2
- package/.cursor/rules/030-wave-execution.mdc +10 -8
- package/.cursor/rules/040-discovery.mdc +6 -6
- package/.cursor/rules/050-plan.mdc +8 -8
- package/CHANGELOG.md +515 -0
- package/README.md +16 -11
- package/agents/analyst.md +1 -1
- package/agents/architect-reviewer.md +1 -1
- package/agents/code-implementer.md +4 -2
- package/agents/db-specialist.md +1 -1
- package/agents/dialectic-deriver.md +1 -1
- package/agents/docs-writer.md +1 -1
- package/agents/memory-proposal-collector.md +7 -5
- package/agents/qa-strategist.md +1 -1
- package/agents/security-reviewer.md +1 -1
- package/agents/session-reviewer.md +42 -1
- package/agents/skill-applied-judge.md +1 -1
- package/agents/test-writer.md +1 -1
- package/agents/ui-developer.md +1 -1
- package/agents/ux-evaluator.md +1 -1
- package/commands/eli5.md +33 -0
- package/commands/release.md +62 -0
- package/commands/test.md +2 -2
- package/docs/components.md +6 -5
- package/docs/migration-v3.md +9 -6
- package/docs/persona-panel.md +3 -1
- package/docs/scope-collision-guard.md +167 -0
- package/docs/session-config-reference.md +31 -8
- package/hooks/_lib/lock-bootstrap.mjs +19 -13
- package/hooks/enforce-scope.mjs +103 -3
- package/hooks/hooks-codex.json +1 -1
- package/hooks/hooks.json +21 -1
- package/hooks/on-session-end.mjs +76 -97
- package/hooks/on-session-start.mjs +195 -104
- package/hooks/on-stop.mjs +127 -12
- package/hooks/post-bash-write-verify.mjs +8 -32
- package/hooks/pre-auq-clarity.mjs +787 -0
- package/hooks/pre-bash-issue-budget.mjs +17 -18
- package/hooks/pre-task-scope-disjoint.mjs +1042 -0
- package/package.json +3 -1
- package/pi/prompts/eli5.md +12 -0
- package/pi/prompts/release.md +12 -0
- package/scripts/auq-audit.mjs +825 -0
- package/scripts/autopilot.mjs +10 -9
- package/scripts/emit-session.mjs +42 -0
- package/scripts/export-hw-learnings.mjs +61 -2
- package/scripts/lib/auq/clarity.mjs +1314 -0
- package/scripts/lib/auq/parse.mjs +1006 -0
- package/scripts/lib/auq/schema.mjs +1457 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
- package/scripts/lib/backlog-scan.mjs +106 -15
- package/scripts/lib/build-live-signals.mjs +7 -3
- package/scripts/lib/ci-status-banner.mjs +267 -77
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +32 -9
- package/scripts/lib/config/vault-integration.mjs +12 -1
- package/scripts/lib/dispatcher/rank.mjs +4 -7
- package/scripts/lib/gates/gate-full.mjs +3 -3
- package/scripts/lib/gates/gate-helpers.mjs +17 -6
- package/scripts/lib/git-config-drift.mjs +471 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +65 -12
- package/scripts/lib/io.mjs +432 -7
- package/scripts/lib/issue-budget.mjs +63 -9
- package/scripts/lib/learnings/select.mjs +157 -3
- package/scripts/lib/memory-cleanup-stamp.mjs +132 -8
- package/scripts/lib/mirror-issues-banner.mjs +266 -0
- package/scripts/lib/named-vault-resolver.mjs +105 -16
- package/scripts/lib/owner-interview.mjs +78 -32
- package/scripts/lib/peer-cards/schema.mjs +6 -2
- package/scripts/lib/peer-discovery.mjs +73 -22
- package/scripts/lib/project-hygiene.mjs +64 -4
- package/scripts/lib/reconcile/renderer.mjs +17 -4
- package/scripts/lib/reconcile/writer.mjs +69 -30
- package/scripts/lib/redact-spans.mjs +89 -0
- package/scripts/lib/resource-probe/evaluate.mjs +330 -149
- package/scripts/lib/resource-probe/probe-platform.mjs +35 -0
- package/scripts/lib/resource-probe.mjs +18 -2
- package/scripts/lib/scope-baseline.mjs +77 -17
- package/scripts/lib/scope-gate.mjs +658 -0
- package/scripts/lib/secret-masker.mjs +262 -0
- package/scripts/lib/session-lock.mjs +34 -10
- package/scripts/lib/session-registry.mjs +9 -1
- package/scripts/lib/spiral-carryover.mjs +23 -2
- package/scripts/lib/state-md/mission-status.mjs +164 -58
- package/scripts/lib/tmux-layout/vcs-detector.mjs +108 -4
- package/scripts/lib/validate/check-agents.mjs +77 -5
- package/scripts/lib/validate/check-auq-clarity.mjs +274 -0
- package/scripts/lib/validate/check-commands.mjs +2 -20
- package/scripts/lib/validate/check-doc-cli-commands.mjs +514 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +48 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +185 -17
- package/scripts/lib/validate/check-rules.mjs +153 -9
- package/scripts/lib/validate/check-skills.mjs +191 -0
- package/scripts/lib/validate/check-test-git-config-target.mjs +665 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +22 -2
- package/scripts/lib/validate/check-untracked-test-deps.mjs +925 -0
- package/scripts/lib/validate/check-unwired-features.mjs +219 -11
- package/scripts/lib/validate/check-vcs-repo-flag.mjs +965 -0
- package/scripts/lib/validate/frontmatter-block.mjs +61 -0
- package/scripts/lib/validate/tier-inference.mjs +46 -8
- package/scripts/lib/vault-backfill/glab.mjs +91 -58
- package/scripts/lib/vault-backfill/manifest.mjs +28 -8
- package/scripts/lib/vault-mirror/namespace.mjs +146 -1
- package/scripts/lib/vault-mirror/process.mjs +264 -31
- package/scripts/lib/vault-mirror/render-sessions.mjs +115 -4
- package/scripts/lib/vault-status/board-writer.mjs +300 -56
- package/scripts/lib/vault-status/narrative-mirror.mjs +119 -5
- package/scripts/lib/vcs-repo-spec.mjs +680 -30
- package/scripts/lib/wave-resource-gate.mjs +67 -73
- package/scripts/materialize-wave-scope.mjs +281 -0
- package/scripts/print-learnings-index.mjs +30 -3
- package/scripts/release.mjs +983 -107
- package/scripts/run-quality-gate.mjs +14 -0
- package/scripts/site-numbers.mjs +1049 -0
- package/scripts/validate-plugin.mjs +64 -0
- package/scripts/validate-wave-scope.mjs +286 -12
- package/scripts/vault-backfill.mjs +32 -5
- package/scripts/vault-mirror.mjs +26 -1
- package/skills/_shared/monitor-patterns.md +24 -4
- package/skills/_shared/parallel-aware-auq.md +30 -24
- package/skills/_shared/parallel-aware-preamble.md +31 -2
- package/skills/_shared/state-ownership.md +49 -6
- package/skills/bootstrap/SKILL.md +2 -1
- package/skills/brainstorm/SKILL.md +18 -18
- package/skills/brainstorm/soul.md +12 -0
- package/skills/claude-md-drift-check/SKILL.md +9 -1
- package/skills/debug/SKILL.md +4 -1
- package/skills/discovery/SKILL.md +28 -24
- package/skills/discovery/issue-templates.md +4 -4
- package/skills/discovery/probes-code.md +2 -2
- package/skills/discovery/probes-feature.md +6 -6
- package/skills/discovery/probes-infra.md +2 -2
- package/skills/discovery/probes-session.md +5 -5
- package/skills/dispatcher/SKILL.md +10 -1
- package/skills/eli5/SKILL.md +43 -0
- package/skills/evolve/SKILL.md +8 -9
- package/skills/frontmatter-guard/SKILL.md +9 -1
- package/skills/gitlab-ops/SKILL.md +73 -59
- package/skills/gitlab-portfolio/SKILL.md +10 -1
- package/skills/grill/SKILL.md +6 -6
- package/skills/grill/soul.md +16 -0
- package/skills/memory-cleanup/SKILL.md +20 -7
- package/skills/npm-publish/SKILL.md +23 -51
- package/skills/peekaboo-driver/SKILL.md +3 -3
- package/skills/persona-panel/SKILL.md +3 -1
- package/skills/plan/SKILL.md +18 -16
- package/skills/plan/mode-feature.md +1 -1
- package/skills/plan/mode-new.md +42 -12
- package/skills/plan/soul.md +12 -0
- package/skills/reconcile/SKILL.md +3 -3
- package/skills/repo-audit/SKILL.md +10 -1
- package/skills/session-end/SKILL.md +97 -22
- package/skills/session-end/metrics-collection.md +1 -1
- package/skills/session-end/phase-3-6-tail.md +37 -2
- package/skills/session-end/session-metrics-write.md +4 -10
- package/skills/session-plan/SKILL.md +2 -2
- package/skills/session-plan/wave-template.md +1 -1
- package/skills/session-start/SKILL.md +82 -36
- package/skills/session-start/phase-2-5-docs-planning.md +8 -8
- package/skills/session-start/phase-4-5-resource-health.md +82 -19
- package/skills/session-start/soul.md +110 -0
- package/skills/spinout/SKILL.md +5 -1
- package/skills/sunset-review/SKILL.md +11 -1
- package/skills/test-runner/SKILL.md +2 -2
- package/skills/tmux-layout/SKILL.md +7 -2
- package/skills/using-orchestrator/SKILL.md +1 -1
- package/skills/vault-mirror/SKILL.md +10 -1
- package/skills/vault-sync/SKILL.md +10 -1
- package/skills/vault-sync/validator.mjs +55 -6
- package/skills/wave-executor/wave-loop.md +64 -12
- package/skills/write-executable-plan/SKILL.md +6 -6
- package/scripts/lib/mission-status-schema.mjs +0 -114
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +0 -8
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +0 -8
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +0 -10
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +0 -8
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +0 -9
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +0 -8
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +0 -7
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +0 -9
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +0 -11
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +0 -3
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +0 -11
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +0 -11
- package/skills/vault-sync/tests/schema-drift.test.mjs +0 -133
|
@@ -37,8 +37,10 @@ import {
|
|
|
37
37
|
yamlQuoteIfNeeded,
|
|
38
38
|
subjectToSlug,
|
|
39
39
|
} from '../vault-mirror/utils.mjs';
|
|
40
|
+
import { matchesModuloRedaction } from '../vault-mirror/process.mjs';
|
|
40
41
|
import { readConfigFile, parseSessionConfig } from '../config.mjs';
|
|
41
42
|
import { validatePathInsideProject } from '../path-utils.mjs';
|
|
43
|
+
import { createSecretValueMasker } from '../secret-masker.mjs';
|
|
42
44
|
|
|
43
45
|
/** Frontmatter sentinel that identifies generator-owned narrative files. */
|
|
44
46
|
export const GENERATOR_MARKER = 'session-orchestrator-vault-status-narrative@1';
|
|
@@ -178,6 +180,80 @@ export function extractNarrative(stateMdContents) {
|
|
|
178
180
|
};
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
// ── Secret masking (#974 / #1025) ───────────────────────────────────────────────
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Mask every env-derived secret VALUE inside an extracted narrative, BEFORE any
|
|
187
|
+
* of it is rendered into the vault file.
|
|
188
|
+
*
|
|
189
|
+
* WHY THIS FILE NEEDS IT AT ALL. `mirrorNarrative` writes into
|
|
190
|
+
* `<vault>/01-projects/<slug>/_session-narrative.md`, and those files are TRACKED
|
|
191
|
+
* AND PUSHED in the operator's vault repo. Its content is STATE.md free text —
|
|
192
|
+
* Deviations, Wave History, mission-status task descriptions — i.e. agent-authored
|
|
193
|
+
* prose that routinely quotes command lines and error output. That is exactly the
|
|
194
|
+
* class a shape-regex cannot catch: the VALUE sits in the prose with no
|
|
195
|
+
* `FOO_TOKEN=` key beside it. A leak here is not fixable by deleting a line — it
|
|
196
|
+
* needs a history rewrite in a foreign repo, which neither this repo's
|
|
197
|
+
* `.gitleaks.toml` nor `check-owner-leakage.mjs` can reach (the vault has no
|
|
198
|
+
* `.husky/` and no active git hooks).
|
|
199
|
+
*
|
|
200
|
+
* WHY THE CALL SITE IS AFTER `extractNarrative`, NOT BEFORE IT. Masking the raw
|
|
201
|
+
* STATE.md string first would let `[REDACTED]` land inside the source
|
|
202
|
+
* FRONTMATTER, where `[…]` reads as a YAML flow sequence. `extractNarrative`
|
|
203
|
+
* parses that frontmatter to obtain `missionStatus`; on a parse error it falls
|
|
204
|
+
* back to `frontmatter = null`, so `missionStatus` becomes null and the Mission
|
|
205
|
+
* Status table SILENTLY disappears from the mirrored file. Masking the parsed
|
|
206
|
+
* narrative keeps the parse on clean input.
|
|
207
|
+
*
|
|
208
|
+
* WHICH OF THE FOUR `vault-mirror/process.mjs` REASONS CARRY HERE — measured, not
|
|
209
|
+
* assumed. Not (1) filename: the basename is the constant `_session-narrative.md`.
|
|
210
|
+
* Not (2) stdout: `mirrorNarrative` prints nothing. Not (3) YAML validity: every
|
|
211
|
+
* output frontmatter field derives from the slug, the date and the repo name —
|
|
212
|
+
* none reads from `narrative`. Only (4) IDEMPOTENCY carries, and it is decisive:
|
|
213
|
+
* `writeNarrative` compares byte-for-byte modulo `updated:`, so masking after
|
|
214
|
+
* that comparison would make every affected file re-render (and re-commit)
|
|
215
|
+
* forever.
|
|
216
|
+
*
|
|
217
|
+
* WHY A GENERIC WALK RATHER THAN FIELD-BY-FIELD. Three of the four narrative keys
|
|
218
|
+
* are strings, but `missionStatus` is an array of objects whose `id`/`task`/
|
|
219
|
+
* `wave`/`status` fields are rendered into the body by `renderMissionTable`. A
|
|
220
|
+
* field-by-field masker would publish a secret sitting in a `task:` description.
|
|
221
|
+
* The walk also stays correct when a future key is added to the narrative shape.
|
|
222
|
+
*
|
|
223
|
+
* NAMED CEILING (build-value BV-004): the masker is built once PER CALL rather
|
|
224
|
+
* than cached in a module-level singleton the way `vault-mirror/process.mjs` does.
|
|
225
|
+
* `mirrorNarrative` runs once per repo per session-end, not in a loop, so the
|
|
226
|
+
* O(env) construction is irrelevant — and a cached masker would silently go stale
|
|
227
|
+
* against a mutated `process.env`, making the result depend on which consumer
|
|
228
|
+
* happened to construct it first in the process. REVISIT TRIGGER: if this module
|
|
229
|
+
* ever gains a per-record or per-wave loop, hoist the masker to the loop head
|
|
230
|
+
* (still not to module scope).
|
|
231
|
+
*
|
|
232
|
+
* Fail-soft by construction: with zero needles the narrative is returned BY
|
|
233
|
+
* REFERENCE (byte-identical downstream), and `mask` passes non-strings through —
|
|
234
|
+
* masking must never be the reason a mirror run dies.
|
|
235
|
+
*
|
|
236
|
+
* @param {{ waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null }} narrative
|
|
237
|
+
* @returns {{ waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null }}
|
|
238
|
+
*/
|
|
239
|
+
function maskNarrative(narrative) {
|
|
240
|
+
const { mask, needleCount } = createSecretValueMasker(process.env);
|
|
241
|
+
if (needleCount === 0) return narrative;
|
|
242
|
+
|
|
243
|
+
const walk = (value) => {
|
|
244
|
+
if (typeof value === 'string') return mask(value);
|
|
245
|
+
if (Array.isArray(value)) return value.map(walk);
|
|
246
|
+
if (value && typeof value === 'object') {
|
|
247
|
+
const out = {};
|
|
248
|
+
for (const [k, v] of Object.entries(value)) out[k] = walk(v);
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
return value;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
return walk(narrative);
|
|
255
|
+
}
|
|
256
|
+
|
|
181
257
|
// ── Pure render ────────────────────────────────────────────────────────────────
|
|
182
258
|
|
|
183
259
|
/**
|
|
@@ -328,10 +404,28 @@ function normalizeUpdated(content) {
|
|
|
328
404
|
* a generator. Return { action: 'skipped-handwritten' }.
|
|
329
405
|
* 2. skip-handwritten: existing file with no `_generator` or a FOREIGN `_generator`
|
|
330
406
|
* marker is hand-authored / owned by another generator → never overwrite.
|
|
331
|
-
* 3. skip-noop: rendered content (modulo the `updated:` timestamp
|
|
332
|
-
*
|
|
407
|
+
* 3. skip-noop: rendered content (modulo the `updated:` timestamp AND modulo any
|
|
408
|
+
* `[REDACTED]` span already on disk) matches the existing content → no write.
|
|
333
409
|
* 4. otherwise write (mkdir -p the parent dir first).
|
|
334
410
|
*
|
|
411
|
+
* WHY STEP 3 IS NOT A PLAIN BYTE COMPARE (#1025). Masking is env-derived and the
|
|
412
|
+
* env is not part of STATE.md, so the two sides of this comparison can be masked
|
|
413
|
+
* DIFFERENTLY: a file written while `FOO_TOKEN` was set carries `[REDACTED]`, and
|
|
414
|
+
* a later run with that var absent renders the RAW value. A byte compare then
|
|
415
|
+
* reports "changed" and this function PUBLISHES THE RAW SECRET into a file that is
|
|
416
|
+
* tracked and pushed in the operator's vault repo — where the vault has no
|
|
417
|
+
* `.husky/` and no active git hooks, so nothing catches it and only a history
|
|
418
|
+
* rewrite in a foreign repo removes it. Reproduced without touching the source
|
|
419
|
+
* between runs: run 1 (env set) → `skipped-noop`, run 2 (env absent) → `written`
|
|
420
|
+
* with the raw value on disk.
|
|
421
|
+
*
|
|
422
|
+
* `matchesModuloRedaction` is IMPORTED from `../vault-mirror/process.mjs`, not
|
|
423
|
+
* re-derived here: `secret-masker.mjs`'s header states this compensation as a
|
|
424
|
+
* contract binding on every consumer that diffs a written artifact against a fresh
|
|
425
|
+
* render, and a contract held at two addresses is a contract that drifts. This
|
|
426
|
+
* module already depends on `vault-mirror/utils.mjs`, and `vault-mirror` imports
|
|
427
|
+
* nothing from `vault-status`, so the direction adds no cycle.
|
|
428
|
+
*
|
|
335
429
|
* @param {{
|
|
336
430
|
* outputPath: string,
|
|
337
431
|
* content: string,
|
|
@@ -379,8 +473,16 @@ export function writeNarrative(opts) {
|
|
|
379
473
|
return { action: 'skipped-handwritten', path: outputPath };
|
|
380
474
|
}
|
|
381
475
|
|
|
382
|
-
// skip-noop: content identical modulo the `updated:` timestamp
|
|
383
|
-
|
|
476
|
+
// skip-noop: content identical modulo the `updated:` timestamp, or —
|
|
477
|
+
// #1025 — identical once every `[REDACTED]` span already on disk is read
|
|
478
|
+
// as a wildcard. See the guard-order note above for why the second arm is
|
|
479
|
+
// load-bearing rather than a nicety.
|
|
480
|
+
const existingNormalized = normalizeUpdated(existingContent);
|
|
481
|
+
const candidateNormalized = normalizeUpdated(content);
|
|
482
|
+
if (
|
|
483
|
+
existingNormalized === candidateNormalized ||
|
|
484
|
+
matchesModuloRedaction(existingNormalized, candidateNormalized)
|
|
485
|
+
) {
|
|
384
486
|
return { action: 'skipped-noop', path: outputPath };
|
|
385
487
|
}
|
|
386
488
|
}
|
|
@@ -466,6 +568,15 @@ function resolveLooseSlug(vaultDir, candidateSlug, fsSeam = {}) {
|
|
|
466
568
|
* resolved vault path is validated to live inside the (home-expanded) vault root
|
|
467
569
|
* before any write.
|
|
468
570
|
*
|
|
571
|
+
* THIS FUNCTION WRITES TO THE OPERATOR'S REAL VAULT UNLESS YOU STOP IT. `vault-dir`
|
|
572
|
+
* resolves HOST-LOCALLY — `SO_VAULT_DIR` > `owner.yaml` `paths.vault-dir` >
|
|
573
|
+
* committed Session Config (`scripts/lib/config/host-paths.mjs`) — so the literal
|
|
574
|
+
* value in a CLAUDE.md you just wrote into a tmp dir LOSES to both overrides. A
|
|
575
|
+
* probe that passes a synthetic `repoRoot` and nothing else has already written
|
|
576
|
+
* into the live vault once (#1025 review). Any caller that must not touch the real
|
|
577
|
+
* vault passes `hostPaths: { env: {}, ownerConfig: undefined }` (see below),
|
|
578
|
+
* `dryRun: true`, or a fully injected `fs`; a tmp `repoRoot` alone is NOT enough.
|
|
579
|
+
*
|
|
469
580
|
* @param {{
|
|
470
581
|
* repoRoot: string,
|
|
471
582
|
* repo: string,
|
|
@@ -543,7 +654,10 @@ export async function mirrorNarrative(opts) {
|
|
|
543
654
|
return { action: 'skipped-no-statemd', path: outputPath };
|
|
544
655
|
}
|
|
545
656
|
|
|
546
|
-
|
|
657
|
+
// #1025: the ONE masking site for the narrative mirror — after the frontmatter
|
|
658
|
+
// parse (so `[REDACTED]` can never break it) and before the render, the
|
|
659
|
+
// idempotency comparison and the write. See maskNarrative above.
|
|
660
|
+
const narrative = maskNarrative(extractNarrative(stateContents));
|
|
547
661
|
const content = renderNarrative({ repo: repoName, narrative, now });
|
|
548
662
|
|
|
549
663
|
return writeNarrative({ outputPath, content, dryRun, fs: injectedFs });
|