@mstar-harness/opencode 3.1.2 → 3.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 (76) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/mstar.js +200 -113
  3. package/harness-commands/codebase-audit.md +3 -3
  4. package/harness-commands/iteration-drive.md +9 -90
  5. package/harness-commands/iteration-loop.md +22 -62
  6. package/harness-commands/iteration-start.md +20 -77
  7. package/harness-commands/pr-deep-review.md +37 -0
  8. package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/SKILL.md +3 -3
  9. package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/references/knowledge-and-designs.md +8 -8
  10. package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/references/plan-files-and-reports.md +6 -6
  11. package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/references/status-and-residuals.md +3 -3
  12. package/harness-skills/mstar-artifacts/templates/README.md +7 -0
  13. package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/templates/plan.main.md +1 -1
  14. package/harness-skills/mstar-audit/SKILL.md +36 -89
  15. package/harness-skills/mstar-audit/references/audit-playbook.md +1 -1
  16. package/harness-skills/mstar-audit/references/codebase-audit.md +85 -0
  17. package/harness-skills/mstar-audit/references/pr-review.md +139 -0
  18. package/harness-skills/mstar-branch-worktree/SKILL.md +5 -5
  19. package/harness-skills/mstar-compound/SKILL.md +4 -4
  20. package/harness-skills/mstar-compound/references/compound-workflow.md +1 -1
  21. package/harness-skills/mstar-compound-refresh/SKILL.md +4 -4
  22. package/harness-skills/mstar-compound-refresh/references/project-knowledge-bootstrap.md +3 -3
  23. package/harness-skills/{mstar-plan-conventions → mstar-conventions}/SKILL.md +9 -9
  24. package/harness-skills/{mstar-plan-conventions → mstar-conventions}/references/artifact-storage-paths.md +13 -13
  25. package/harness-skills/{mstar-plan-conventions → mstar-conventions}/references/harness-bootstrap-and-agents-layering.md +5 -5
  26. package/harness-skills/mstar-design-md/SKILL.md +3 -3
  27. package/harness-skills/mstar-dispatch-gates/SKILL.md +3 -3
  28. package/harness-skills/mstar-engine-legacy/SKILL.md +1 -1
  29. package/harness-skills/mstar-engine-legacy/references/lease-protocol.md +1 -1
  30. package/harness-skills/mstar-engine-legacy/references/status-field-history.md +1 -1
  31. package/harness-skills/mstar-harness-core/SKILL.md +21 -20
  32. package/harness-skills/mstar-host/references/_shared/plan-mode-bridge-core.md +5 -5
  33. package/harness-skills/mstar-host/references/cursor-plan-mode-bridge.md +7 -7
  34. package/harness-skills/mstar-host/references/cursor.md +1 -1
  35. package/harness-skills/mstar-host/references/kimi-plan-mode-bridge.md +1 -1
  36. package/harness-skills/mstar-host/references/omp-plan-mode-bridge.md +1 -1
  37. package/harness-skills/mstar-host/references/parallel-dispatch.md +1 -1
  38. package/harness-skills/mstar-host/references/zcode-plan-mode-bridge.md +1 -1
  39. package/harness-skills/mstar-iteration/SKILL.md +21 -15
  40. package/harness-skills/mstar-iteration/references/command-shared-invariants.md +62 -0
  41. package/harness-skills/mstar-iteration/references/iteration-artifact-boundaries.md +1 -1
  42. package/harness-skills/mstar-iteration/references/phase-2-worktree-lease.md +5 -5
  43. package/harness-skills/mstar-iteration/references/phase-3-iteration-close.md +1 -1
  44. package/harness-skills/mstar-iteration/references/phase-4-5-pr-delivery.md +1 -1
  45. package/harness-skills/mstar-iteration/references/phase5-helper-discovery.md +1 -1
  46. package/harness-skills/mstar-phase-gates/SKILL.md +5 -5
  47. package/harness-skills/mstar-project-governance/SKILL.md +9 -9
  48. package/harness-skills/mstar-review-qc/SKILL.md +4 -4
  49. package/harness-skills/mstar-roles/SKILL.md +7 -7
  50. package/harness-skills/mstar-roles/references/_shared/leaf-executor-core.md +20 -2
  51. package/harness-skills/mstar-roles/references/architect.md +7 -10
  52. package/harness-skills/mstar-roles/references/code-reviewer.md +7 -10
  53. package/harness-skills/mstar-roles/references/frontend-dev.md +6 -5
  54. package/harness-skills/mstar-roles/references/fullstack-dev-shared.md +6 -5
  55. package/harness-skills/mstar-roles/references/ops-engineer.md +3 -7
  56. package/harness-skills/mstar-roles/references/product-manager.md +8 -11
  57. package/harness-skills/mstar-roles/references/project-manager/dispatch-and-assignment.md +1 -1
  58. package/harness-skills/mstar-roles/references/project-manager/plan-management.md +3 -3
  59. package/harness-skills/mstar-roles/references/project-manager/qa-trigger-matrix.md +2 -2
  60. package/harness-skills/mstar-roles/references/project-manager/qc-and-residuals.md +3 -3
  61. package/harness-skills/mstar-roles/references/project-manager.md +5 -5
  62. package/harness-skills/mstar-roles/references/prompt-engineer.md +4 -7
  63. package/harness-skills/mstar-roles/references/qa-engineer/acceptance-gate.md +2 -2
  64. package/harness-skills/mstar-roles/references/qa-engineer.md +4 -8
  65. package/harness-skills/mstar-roles/references/qc-specialist/report-template.md +1 -1
  66. package/harness-skills/mstar-roles/references/qc-specialist/reviewer-workflow.md +1 -1
  67. package/harness-skills/mstar-roles/references/qc-specialist-shared.md +5 -8
  68. package/harness-skills/mstar-roles/references/writing-specialist.md +3 -6
  69. package/harness-skills/mstar-sdd/SKILL.md +2 -2
  70. package/harness-skills/mstar-sdd/references/file-handoffs.md +1 -1
  71. package/harness-skills/mstar-strategy/SKILL.md +8 -38
  72. package/package.json +1 -1
  73. package/harness-skills/mstar-plan-artifacts/templates/README.md +0 -7
  74. /package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/references/plan-quality-bar.md +0 -0
  75. /package/harness-skills/{mstar-plan-artifacts → mstar-artifacts}/templates/status.empty.json +0 -0
  76. /package/harness-skills/{mstar-plan-conventions → mstar-conventions}/references/effort-estimation.md +0 -0
package/CHANGELOG.md CHANGED
@@ -6,6 +6,33 @@ The monorepo root [CHANGELOG.md](../../CHANGELOG.md) summarizes cross-surface re
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.2.0] - 2026-08-23
10
+
11
+ ### Harness
12
+
13
+ - Hoisted the shared plan-output contract out of `references/codebase-audit.md` into the `mstar-audit` SKILL.md core as **`## Plan output (all variants)`**: write-only-on-selection boundary, `{PLAN_DIR}/audit-<date>/` layout (README index + numbered plan files), `plan.main.md` + plan-quality-bar enrichment, Status-block fields + status values, `git rev-parse --short HEAD` commit stamp, and the four handoff steps (promote / state machine / fast-track Prepare / SDD or inline dispatch). Both variants (`codebase-audit`, `pr-review`) and both commands (`/codebase-audit`, `/pr-deep-review`) now cite the core section; `pr-review.md` § Plan output carries no `codebase-audit.md` cites. Engine audit Status-block and scaffold validators repoint their spec cites to `mstar-audit SKILL.md § Plan output`, and `pr-review.md` § Evidence rules now cites `finding-format.md` § What disqualifies a finding. Closes residual R1 (hoist when a third variant arrives — that condition is now met by the `pr` variant) early.
14
+ - Restructured `mstar-audit` into a **variant carrier**: SKILL.md now holds the common core (hard rules, recon, attack-and-vet discipline, variant dispatch table, output-format contract), with full codebase-audit detail moved verbatim to `references/codebase-audit.md` (Phase 2 category fan-out + subagent-prompt requirements, effort table, scope variants, Phase 4 plan writing, audit index / plan-file output templates, `mstar audit scaffold` callout, handoff to execution). `pr-deep-review` no longer loads full-audit-only content; pointer surfaces (`references/pr-review.md`, both commands, `code-reviewer` role, `mstar-harness-core` index) cite the common core + the correct variant reference.
15
+ - Added a **generic deep PR review** command (`pr-deep-review`) + `mstar-audit` `pr` scope variant: worktree-isolated, evidence-first, verdict-producing review with concern lenses, linked-issue hygiene, and batch sibling-PR support.
16
+ - Renamed `mstar-plan-conventions` → **`mstar-conventions`** and `mstar-plan-artifacts` → **`mstar-artifacts`**: the two skills are general harness conventions (paths, artifacts), not plan-specific, so the `plan-` prefix was dropped. All live surfaces swept (`skills/**` load orders, index rows and cross-cites, `commands/**`, `AGENTS.md`, `README.md` + `README_CN.md` skill tables, `docs/cli.md`, `.cursor/` routing-eval fixtures + local validation, `scripts/` guards, engine/dsh/cli source comments and path literals, dsh test expectations). Historical changelogs and engine test-fixture prose are untouched — old names there are correct as historical record.
17
+
18
+ - Version alignment with harness **3.2.0** (no OpenCode package API change).
19
+
20
+ See root [CHANGELOG.md](../../CHANGELOG.md) **3.2.0**.
21
+
22
+ ## [3.1.3] - 2026-08-23
23
+
24
+ ### Harness
25
+
26
+ - **`mstar audit promote` (CLI)**: selected audit plans can now enter the v2 workflow lifecycle as `type: plan` — `promoteAuditPlans` writes the workflow snapshot (`{HARNESS_DIR}/workflows/<id>/snapshot.json`, one Todo `PlanRow` per selected plan, `id` + `title` + `file`) **before** registering the workflow in root `status.json`, so the snapshot-before-register invariant holds. `--plans` accepts the README Plan column id, stem, or basename; `--workflow` defaults to the `audit-<date>` dir basename; `--harness` defaults to the resolved `{HARNESS_DIR}`. Promote stays an explicit post-selection action — the audit itself never registers (advisory contract preserved).
27
+ - **Engine**: `promoteAuditPlans` exported from `@mstar-harness/engine` (titles come from the audit README index, falling back to `readPlanFileSummary`).
28
+ - **Harness skills**: `mstar-audit` Handoff step 1 now names `mstar audit promote <audit-dir> --plans <ids>` as the first-class v2 path (manual `mstar-plan-artifacts` wording kept as fallback).
29
+ - **Thinned the iteration slash commands** (`/iteration-start`, `/iteration-drive`, `/iteration-loop`) into wrappers: shared PM invariants, session todos, the continuous-execution STOP list, and the assignment-preflight bash (warn-only + `enforcement: hard` fail-fast) now live once in `skills/mstar-iteration/references/command-shared-invariants.md`, and `mstar-iteration` points at it. Frontmatter (`name` / `description` / `agent` / `input`) and each command's unique bits (start grill-me, drive helper discovery, loop do-not-load-grill-me) are unchanged.
30
+ - **Trigger-strong `mstar-iteration` description**: the skill now loads for "start an iteration" / "drive the iteration" / "run an autonomous loop" even when no `/iteration-*` slash command fires; phase labels no longer treat command names as phase names.
31
+
32
+ - Version alignment with harness **3.1.3** (no OpenCode package API change).
33
+
34
+ See root [CHANGELOG.md](../../CHANGELOG.md) **3.1.3**.
35
+
9
36
  ## [3.1.2] - 2026-08-21
10
37
 
11
38
  ### Harness
package/dist/mstar.js CHANGED
@@ -63,11 +63,11 @@ __export(exports_engine, {
63
63
  releaseLease: () => releaseLease,
64
64
  registerWorkflow: () => registerWorkflow,
65
65
  referenceExists: () => referenceExists,
66
- redactSecrets: () => redactSecrets,
67
66
  readProgressLedger: () => readProgressLedger,
68
67
  readJson: () => readJson,
69
68
  readHarnessVersion: () => readHarnessVersion,
70
69
  pushCadenceProbe: () => pushCadenceProbe,
70
+ promoteAuditPlans: () => promoteAuditPlans,
71
71
  planQualityBar: () => planQualityBar,
72
72
  planExecutionLeaseLocations: () => planExecutionLeaseLocations,
73
73
  parseMstarc: () => parseMstarc,
@@ -185,11 +185,11 @@ import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync a
185
185
  import { join as join9 } from "node:path";
186
186
  import { copyFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync8, readdirSync as readdirSync6, writeFileSync as writeFileSync4 } from "node:fs";
187
187
  import { dirname as dirname7, isAbsolute as isAbsolute6, join as join10, relative as relative3, resolve as resolve8, sep as sep2 } from "node:path";
188
- import { mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
189
- import { join as join11, resolve as resolve9 } from "node:path";
190
- import { existsSync as existsSync7, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
191
- import { basename as basename4, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve10, sep as sep3 } from "node:path";
192
- import { existsSync as existsSync8 } from "node:fs";
188
+ import { existsSync as existsSync7, mkdirSync as mkdirSync7, readdirSync as readdirSync7, readFileSync as readFileSync9, rmdirSync as rmdirSync2, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
189
+ import { basename as basename4, join as join11, resolve as resolve9, sep as sep3 } from "node:path";
190
+ import { existsSync as existsSync8, readdirSync as readdirSync8, readFileSync as readFileSync10 } from "node:fs";
191
+ import { basename as basename5, isAbsolute as isAbsolute7, join as join12, relative as relative4, resolve as resolve10, sep as sep4 } from "node:path";
192
+ import { existsSync as existsSync9 } from "node:fs";
193
193
  import { join as join13 } from "node:path";
194
194
  function applyEnforcement(gate, opts) {
195
195
  return { ...gate, hardBlocked: opts.hard && gate.violations.length > 0 };
@@ -1434,40 +1434,41 @@ function validateStatusV2(docOrPath, opts = {}) {
1434
1434
  violations.push(violation4("medium", "status.workflow.mismatched-type", `workflows[] entry ${JSON.stringify(label)} type ${JSON.stringify(entry.type)} does not match its snapshot type ${JSON.stringify(snapshot.type)} — the root entry mirrors the snapshot; align them`));
1435
1435
  }
1436
1436
  if (typeof entry.started_at === "string" && typeof snapshot.started_at === "string" && entry.started_at !== snapshot.started_at) {
1437
- violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — the root entry mirrors the snapshot; align them`));
1437
+ violations.push(violation4("medium", "status.workflow.mismatched-started-at", `workflows[] entry ${JSON.stringify(label)} started_at ${JSON.stringify(entry.started_at)} does not match its snapshot started_at ${JSON.stringify(snapshot.started_at)} — workflow ${JSON.stringify(label)} collided with another writer (e.g. a concurrent/re-run \`audit promote\` with the same workflow id rewrote the snapshot); the root entry mirrors the snapshot align them or remove the colliding workflow`));
1438
1438
  }
1439
1439
  }
1440
1440
  }
1441
1441
  return { ok: violations.length === 0, violations };
1442
1442
  }
1443
+ function registerWorkflowEntryLocked(statusPath, entry) {
1444
+ const harnessDir = dirname5(statusPath);
1445
+ const current = readJson(statusPath);
1446
+ const fresh = Object.keys(current).length === 0;
1447
+ const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
1448
+ if (!fresh && !Array.isArray(doc.workflows)) {
1449
+ throw new Error("refusing to modify status.json: workflows must be an array — a v1 root must be migrated first (run `mstar migrate`)");
1450
+ }
1451
+ const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
1452
+ if (existing >= 0) {
1453
+ doc.workflows[existing] = entry;
1454
+ } else {
1455
+ doc.workflows.push(entry);
1456
+ }
1457
+ doc.updated_at = todayString();
1458
+ const gate = validateStatusV2(doc, { harnessDir });
1459
+ if (!gate.ok) {
1460
+ throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
1461
+ }
1462
+ writeJson(statusPath, doc);
1463
+ return doc;
1464
+ }
1443
1465
  async function registerWorkflow(root, entry) {
1444
1466
  const entryGate = validateWorkflowEntry(entry);
1445
1467
  if (!entryGate.ok) {
1446
1468
  throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
1447
1469
  }
1448
1470
  const statusPath = resolve5(root);
1449
- const harnessDir = dirname5(statusPath);
1450
- return withStatusWriteLock(statusPath, () => {
1451
- const current = readJson(statusPath);
1452
- const fresh = Object.keys(current).length === 0;
1453
- const doc = fresh ? { version: 2, updated_at: todayString(), workflows: [] } : current;
1454
- if (!fresh && !Array.isArray(doc.workflows)) {
1455
- throw new Error("refusing to modify status.json: workflows must be an array — a v1 root must be migrated first (run `mstar migrate`)");
1456
- }
1457
- const existing = doc.workflows.findIndex((wf) => wf.id === entry.id);
1458
- if (existing >= 0) {
1459
- doc.workflows[existing] = entry;
1460
- } else {
1461
- doc.workflows.push(entry);
1462
- }
1463
- doc.updated_at = todayString();
1464
- const gate = validateStatusV2(doc, { harnessDir });
1465
- if (!gate.ok) {
1466
- throw new Error(`refusing to write invalid status.json: ${gate.violations.map((v) => v.message).join("; ")}`);
1467
- }
1468
- writeJson(statusPath, doc);
1469
- return doc;
1470
- });
1471
+ return withStatusWriteLock(statusPath, () => registerWorkflowEntryLocked(statusPath, entry));
1471
1472
  }
1472
1473
  async function unregisterWorkflow(root, id) {
1473
1474
  if (typeof id !== "string" || id.trim() === "") {
@@ -3012,9 +3013,11 @@ async function applyMigratePlan(plan) {
3012
3013
  if (!gate2.ok) {
3013
3014
  throw new Error(`refusing to apply migration: invalid project register: ${gate2.violations.map((v) => v.message).join("; ")}`);
3014
3015
  }
3015
- const filePath = projectTargetOf(plan.register.file);
3016
- mkdirSync6(dirname7(filePath), { recursive: true });
3017
- writeJson(filePath, plan.register.data);
3016
+ if (Object.keys(plan.register.data.entries ?? {}).length > 0) {
3017
+ const filePath = projectTargetOf(plan.register.file);
3018
+ mkdirSync6(dirname7(filePath), { recursive: true });
3019
+ writeJson(filePath, plan.register.data);
3020
+ }
3018
3021
  }
3019
3022
  if (plan.roadmap !== null) {
3020
3023
  const filePath = projectTargetOf(plan.roadmap.file);
@@ -3444,14 +3447,14 @@ function validateAuditStatusBlocks(planText) {
3444
3447
  const violations = [];
3445
3448
  const blocks = parseStatusBlocks(planText);
3446
3449
  if (blocks.length === 0) {
3447
- violations.push(violation9("medium", "audit.status.missing-block", "no `## Status` block found — audit plan files carry the Status block fields (mstar-audit SKILL § Plan files)", "add a `## Status` block with Priority, Effort, Risk, Depends on, Category, Planned at"));
3450
+ violations.push(violation9("medium", "audit.status.missing-block", "no `## Status` block found — audit plan files carry the Status block fields (mstar-audit SKILL.md § Plan output)", "add a `## Status` block with Priority, Effort, Risk, Depends on, Category, Planned at"));
3448
3451
  return { ok: false, violations };
3449
3452
  }
3450
3453
  blocks.forEach((block, index) => {
3451
3454
  const label = blocks.length > 1 ? ` #${index + 1}` : "";
3452
3455
  for (const field of AUDIT_STATUS_FIELDS) {
3453
3456
  if (!block.fields.has(field)) {
3454
- violations.push(violation9("medium", "audit.status.missing-field", `Status block${label} missing required field "${field}" (mstar-audit SKILL § Plan files)`, `add \`- **${field}**: <value>\` to the Status block`));
3457
+ violations.push(violation9("medium", "audit.status.missing-field", `Status block${label} missing required field "${field}" (mstar-audit SKILL.md § Plan output)`, `add \`- **${field}**: <value>\` to the Status block`));
3455
3458
  }
3456
3459
  }
3457
3460
  const check = (field, pattern, code, expected) => {
@@ -3459,7 +3462,7 @@ function validateAuditStatusBlocks(planText) {
3459
3462
  if (value === undefined)
3460
3463
  return;
3461
3464
  if (!pattern.test(value)) {
3462
- violations.push(violation9("medium", code, `Status block${label} "${field}" = "${value}" — expected ${expected} (mstar-audit SKILL § Plan files)`, `fix \`- **${field}**:\` to one of: ${expected}`));
3465
+ violations.push(violation9("medium", code, `Status block${label} "${field}" = "${value}" — expected ${expected} (mstar-audit SKILL.md § Plan output)`, `fix \`- **${field}**:\` to one of: ${expected}`));
3463
3466
  }
3464
3467
  };
3465
3468
  check("Priority", /^P[123]$/, "audit.status.invalid-priority", "P1 | P2 | P3");
@@ -3471,60 +3474,6 @@ function validateAuditStatusBlocks(planText) {
3471
3474
  });
3472
3475
  return { ok: violations.length === 0, violations };
3473
3476
  }
3474
- function buildLineStarts(text) {
3475
- const starts = [0];
3476
- for (let i = 0;i < text.length; i++) {
3477
- if (text[i] === `
3478
- `)
3479
- starts.push(i + 1);
3480
- }
3481
- return starts;
3482
- }
3483
- function lineAt(starts, index) {
3484
- let lo = 0;
3485
- let hi = starts.length - 1;
3486
- while (lo < hi) {
3487
- const mid = lo + hi + 1 >> 1;
3488
- if (starts[mid] <= index)
3489
- lo = mid;
3490
- else
3491
- hi = mid - 1;
3492
- }
3493
- return lo + 1;
3494
- }
3495
- function redactSecrets(text, filePath) {
3496
- const starts = buildLineStarts(text);
3497
- const marker = (type, index) => `[REDACTED ${type}@${lineAt(starts, index)}${filePath === undefined ? "" : ` in ${filePath}`}]`;
3498
- const replacements = [];
3499
- const findings = [];
3500
- for (const pattern of WHOLE_MATCH_PATTERNS) {
3501
- for (const match of text.matchAll(pattern.re)) {
3502
- if (match.index === undefined)
3503
- continue;
3504
- replacements.push({ index: match.index, length: match[0].length, text: marker(pattern.type, match.index) });
3505
- findings.push({ line: lineAt(starts, match.index), type: pattern.type });
3506
- }
3507
- }
3508
- for (const pattern of VALUE_PATTERNS) {
3509
- for (const match of text.matchAll(pattern.re)) {
3510
- if (match.index === undefined)
3511
- continue;
3512
- const type = pattern.typeOf(match[2]);
3513
- const replacement = `${match[1]}${match[2]}${match[3]}${match[4]}${marker(type, match.index)}`;
3514
- replacements.push({ index: match.index, length: match[0].length, text: replacement });
3515
- findings.push({ line: lineAt(starts, match.index), type });
3516
- }
3517
- }
3518
- replacements.sort((a, b) => b.index - a.index);
3519
- let out = text;
3520
- for (const r of replacements)
3521
- out = out.slice(0, r.index) + r.text + out.slice(r.index + r.length);
3522
- const deduped = new Map;
3523
- for (const f of findings)
3524
- deduped.set(`${f.line}:${f.type}`, f);
3525
- const sorted = [...deduped.values()].sort((a, b) => a.line - b.line || a.type.localeCompare(b.type));
3526
- return { text: out, findings: sorted };
3527
- }
3528
3477
  function slugify(title) {
3529
3478
  return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3530
3479
  }
@@ -3659,6 +3608,137 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
3659
3608
  }));
3660
3609
  return { outDir: resolve9(outDir), date, files: written, nextNumber: next };
3661
3610
  }
3611
+ async function promoteAuditPlans(outDir, selected, options) {
3612
+ if (selected.length === 0) {
3613
+ throw new Error("promoteAuditPlans: at least one plan id must be selected (--plans 001,002,…)");
3614
+ }
3615
+ if (typeof options.harnessDir !== "string" || options.harnessDir.trim() === "") {
3616
+ throw new Error("promoteAuditPlans: options.harnessDir is required (must contain status.json + workflows/)");
3617
+ }
3618
+ const workflowId = options.workflowId ?? basename4(resolve9(outDir));
3619
+ assertSafePathComponent(workflowId, "workflow id");
3620
+ const harnessDir = resolve9(options.harnessDir);
3621
+ const statusPath = join11(harnessDir, "status.json");
3622
+ const workflowDir = join11(harnessDir, "workflows", workflowId);
3623
+ const snapshotPath = join11(workflowDir, WORKFLOW_SNAPSHOT_FILE);
3624
+ const planFiles = resolveSelectedPlanFiles(outDir, selected);
3625
+ const indexRows = readExecutionOrderIndex(outDir);
3626
+ const plans = planFiles.map((planFile) => {
3627
+ const stem = planFile.replace(/\.md$/, "");
3628
+ const num = stem.slice(0, 3);
3629
+ const indexRow = indexRows.get(num);
3630
+ const title = indexRow?.title ?? readPlanFileSummary(join11(outDir, planFile)).title;
3631
+ return {
3632
+ id: stem,
3633
+ title,
3634
+ file: planFileRel(outDir, planFile),
3635
+ status: "Todo"
3636
+ };
3637
+ });
3638
+ const now = new Date;
3639
+ const snapshot = {
3640
+ schema_version: 1,
3641
+ id: workflowId,
3642
+ type: "plan",
3643
+ status: "running",
3644
+ started_at: now.toISOString(),
3645
+ updated_at: now.toISOString().slice(0, 10),
3646
+ plans
3647
+ };
3648
+ const entry = {
3649
+ id: workflowId,
3650
+ type: "plan",
3651
+ started_at: snapshot.started_at,
3652
+ dir: `workflows/${workflowId}`
3653
+ };
3654
+ const entryGate = validateWorkflowEntry(entry);
3655
+ if (!entryGate.ok) {
3656
+ throw new Error(`refusing to register invalid workflow entry: ${entryGate.violations.map((v) => v.message).join("; ")}`);
3657
+ }
3658
+ await withStatusWriteLock(statusPath, () => {
3659
+ if (existsSync7(snapshotPath)) {
3660
+ throw new Error(`refusing to promote audit plans: workflow ${JSON.stringify(workflowId)} already exists ` + `(snapshot at ${snapshotPath}) — re-promote would drop its registered plan rows; ` + `remove that workflow before promoting again`);
3661
+ }
3662
+ mkdirSync7(workflowDir, { recursive: true });
3663
+ try {
3664
+ writeJson(snapshotPath, snapshot);
3665
+ registerWorkflowEntryLocked(statusPath, entry);
3666
+ } catch (error) {
3667
+ rmSync(snapshotPath, { force: true });
3668
+ try {
3669
+ if (readdirSync7(workflowDir).length === 0) {
3670
+ rmdirSync2(workflowDir);
3671
+ }
3672
+ } catch {}
3673
+ throw error;
3674
+ }
3675
+ return { workflowId, snapshotPath };
3676
+ });
3677
+ return { workflowId, snapshotPath };
3678
+ }
3679
+ function resolveSelectedPlanFiles(outDir, selected) {
3680
+ const files = readdirSync7(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f)).sort();
3681
+ const byNum = new Map;
3682
+ const byStem = new Map;
3683
+ for (const file of files) {
3684
+ const stem = file.replace(/\.md$/, "");
3685
+ if (!byNum.has(stem.slice(0, 3))) {
3686
+ byNum.set(stem.slice(0, 3), file);
3687
+ }
3688
+ byStem.set(stem, file);
3689
+ }
3690
+ const resolved = [];
3691
+ const seen = new Set;
3692
+ for (const id of selected) {
3693
+ const file = byNum.get(id) ?? byStem.get(id) ?? byStem.get(id.replace(/\.md$/, ""));
3694
+ if (file === undefined) {
3695
+ throw new Error(`promoteAuditPlans: selected plan ${JSON.stringify(id)} does not match any NNN-*.md file in ${resolve9(outDir)}`);
3696
+ }
3697
+ if (!seen.has(file)) {
3698
+ seen.add(file);
3699
+ resolved.push(file);
3700
+ }
3701
+ }
3702
+ return resolved;
3703
+ }
3704
+ function readExecutionOrderIndex(outDir) {
3705
+ const readmePath = join11(outDir, "README.md");
3706
+ let text;
3707
+ try {
3708
+ text = readFileSync9(readmePath, "utf8");
3709
+ } catch {
3710
+ return new Map;
3711
+ }
3712
+ const rows = new Map;
3713
+ const lines = text.split(`
3714
+ `);
3715
+ let inSection = false;
3716
+ for (const line of lines) {
3717
+ if (/^##\s+Execution order & status/.test(line)) {
3718
+ inSection = true;
3719
+ continue;
3720
+ }
3721
+ if (inSection && /^#/.test(line)) {
3722
+ break;
3723
+ }
3724
+ if (!inSection)
3725
+ continue;
3726
+ const cells = line.split(/(?<!\\)\|/).map((c) => c.trim());
3727
+ if (cells.length >= 3 && /^\d{3}$/.test(cells[1])) {
3728
+ rows.set(cells[1], { title: cells[2].replace(/\\\|/g, "|") });
3729
+ }
3730
+ }
3731
+ return rows;
3732
+ }
3733
+ function planFileRel(outDir, planFile) {
3734
+ const resolved = resolve9(outDir);
3735
+ const parts = resolved.split(sep3);
3736
+ const plansIdx = parts.lastIndexOf("plans");
3737
+ if (plansIdx >= 0) {
3738
+ return `${parts.slice(plansIdx + 1).join(sep3)}${sep3}${planFile}`;
3739
+ }
3740
+ return planFile;
3741
+ }
3662
3742
  function violation10(severity, code, message, fix) {
3663
3743
  return { ok: false, severity, code, message, fix };
3664
3744
  }
@@ -3875,7 +3955,7 @@ function referenceExists(repoRoot, docText) {
3875
3955
  for (const { ref, isSymbol, module } of refs) {
3876
3956
  if (!isSymbol || module === undefined) {
3877
3957
  const candidate = ref.replace(LINE_SUFFIX_RE, "").replace(ANCHOR_RE, "");
3878
- if (existsSync7(resolve10(repoRoot, candidate))) {
3958
+ if (existsSync8(resolve10(repoRoot, candidate))) {
3879
3959
  checked++;
3880
3960
  } else {
3881
3961
  violations.push(violation10("medium", "compound.reference.missing-file", `referenced path \`${ref}\` does not exist under ${repoRoot} (compound-refresh Phase 2: referenced code still exists?)`, "update the doc to reference an existing path, or delete the stale reference"));
@@ -3906,7 +3986,7 @@ function collectKnowledgeDocs(dir) {
3906
3986
  if (entry.isDirectory()) {
3907
3987
  stack.push(full);
3908
3988
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "index.md") {
3909
- docs.push(relative4(dir, full).split(sep3).join("/"));
3989
+ docs.push(relative4(dir, full).split(sep4).join("/"));
3910
3990
  }
3911
3991
  }
3912
3992
  }
@@ -3923,7 +4003,7 @@ function normalizeIndexRef(cell) {
3923
4003
  function assertIndexRows(knowledgeDir) {
3924
4004
  const violations = [];
3925
4005
  const readmePath = join12(knowledgeDir, "README.md");
3926
- if (!existsSync7(readmePath)) {
4006
+ if (!existsSync8(readmePath)) {
3927
4007
  violations.push(violation10("medium", "compound.index.missing-readme", `missing ${readmePath} — the knowledge index is required (mstar-compound Phase 6: every doc gets a README.md row)`, "create knowledge/README.md with a Document / Source Plan / Description / Status table"));
3928
4008
  return { ok: false, violations };
3929
4009
  }
@@ -3955,7 +4035,7 @@ function compoundRefreshScope(harnessDir, projectRoot) {
3955
4035
  ];
3956
4036
  }
3957
4037
  function isFileLikeRoot(root) {
3958
- return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename4(root));
4038
+ return /^[^.]*\.[A-Za-z0-9]{1,10}$/.test(basename5(root));
3959
4039
  }
3960
4040
  function scopeGuard(path, allowedRoots) {
3961
4041
  const resolved = resolve10(path);
@@ -3964,7 +4044,7 @@ function scopeGuard(path, allowedRoots) {
3964
4044
  if (isFileLikeRoot(r)) {
3965
4045
  if (resolved === r)
3966
4046
  return { ok: true, violations: [] };
3967
- } else if (resolved === r || resolved.startsWith(r + sep3)) {
4047
+ } else if (resolved === r || resolved.startsWith(r + sep4)) {
3968
4048
  return { ok: true, violations: [] };
3969
4049
  }
3970
4050
  }
@@ -4092,7 +4172,7 @@ function planQualityBar(planText) {
4092
4172
  if (token !== null) {
4093
4173
  const text = trimmed.length > 80 ? `${trimmed.slice(0, 77)}...` : trimmed;
4094
4174
  findings.push({ token, line: i + 1, text });
4095
- violations.push(violation11("medium", "lint.plan-quality.placeholder", `placeholder token "${token}" at line ${i + 1}: "${text}"`, "replace the placeholder with concrete content before locking the plan (mstar-plan-artifacts/references/plan-quality-bar.md; templates/plan.main.md placeholder scan)"));
4175
+ violations.push(violation11("medium", "lint.plan-quality.placeholder", `placeholder token "${token}" at line ${i + 1}: "${text}"`, "replace the placeholder with concrete content before locking the plan (mstar-artifacts/references/plan-quality-bar.md; templates/plan.main.md placeholder scan)"));
4096
4176
  }
4097
4177
  }
4098
4178
  return { ok: violations.length === 0, violations, findings };
@@ -4186,7 +4266,7 @@ function validateRoleMapping(rolesDir, options = {}) {
4186
4266
  const violations = [];
4187
4267
  const referenceById = new Map(mapping.map((m) => [m.agentId, m.reference]));
4188
4268
  for (const { agentId, reference } of mapping) {
4189
- if (!existsSync8(join13(rolesDir, reference))) {
4269
+ if (!existsSync9(join13(rolesDir, reference))) {
4190
4270
  violations.push(violation12("medium", "roles.mapping.reference.missing", `role "${agentId}" maps to ${reference} which does not exist under ${rolesDir} (mstar-roles § Role Reference Mapping)`, `create ${join13(rolesDir, reference)} or fix the mapping row`));
4191
4271
  }
4192
4272
  }
@@ -4382,7 +4462,7 @@ var SEVERITY_ORDER, MSTARC_FILE = ".mstarc", MSTARC_SECTION = "config", MSTARC_H
4382
4462
  !.agents/knowledge/**
4383
4463
  !.agents/specs/
4384
4464
  !.agents/specs/**
4385
- `, GITIGNORE_PROCESS_ENTRIES, GITIGNORE_PROCESS_ENTRIES_AGENTS, DATE_PART, RFC3339_Z_RE, DATE_ONLY_RE, STATUS_WRITE_LOCKDIR = ".status-write.lockdir", LOCKDIR_HOLDER_PID = "holder.pid", heldLockDirs, BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"', REQUIRED_FIELDS, ASSIGNMENT_ENFORCEMENT_BOLD_RE, ASSIGNMENT_ENFORCEMENT_PLAIN_RE, COMPASS_ENFORCEMENT_RE, ASSIGNMENT_BODY_START_RE, ASSIGNMENT_HEADING_RE, ASSIGNMENT_FIELD_RE, WORKFLOW_SNAPSHOT_FILE = "snapshot.json", WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_TERMINAL_STATUSES, WORKFLOW_LIFECYCLE_TYPES, DATE_RE, PLAN_STATUSES, RESIDUAL_DECISIONS, RESIDUAL_LIFECYCLES, validateStatus, DEFAULT_PROBE_TIMEOUT_MS = 1e4, QC_ALIGNMENT_FIELDS, SddScriptError, GIT_CAPTURE_MAX_BYTES, COMPASS_STATUSES, DATE_RE2, PLAN_STATUS_DONE = "Done", COMPASS_FILE = "delivery-compass.md", INDEX_README = "README.md", INDEX_HEADER = "| Iteration | Path | Description | Status |", PROJECT_ROADMAP_FILE = "roadmap.md", PROJECT_REFERENCES_DIR = "references", PROJECT_REGISTER_FILE = "residuals.json", _DEFAULT_PROJECT = "_default", ROADMAP_STATUSES, DATE_RE3, ROLLUP_FIELDS, MIGRATE_STATUS_FILE = "status.json", ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json", NOTES_LEDGER_FILE = "notes.jsonl", DATE_RE4, ROOT_METADATA_LIFT_KEYS, RAW_GROUP = "__raw", isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v), HEX_RE, OKLCH_RE, PX_RE, PLACEHOLDER_RE, TYPOGRAPHY_PROPS, REF_RE, REF_GROUPS, isPlaceholder = (value) => PLACEHOLDER_RE.test(value), PARITY_GROUPS, GRAY_STEPS, ALPHA_STEPS, ACCENT_SCALES, L3_BODY_ITEM_IDS, LEVEL_RANK, AUDIT_PRIORITIES, AUDIT_EFFORTS, AUDIT_RISKS, AUDIT_CATEGORIES, AUDIT_STATUS_FIELDS, WHOLE_MATCH_PATTERNS, VALUE_PATTERNS, escapeCell = (value) => value.replace(/\|/g, "\\|"), truncate = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value, KNOWLEDGE_REQUIRED_FIELDS, KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_BUG_PROBLEM_TYPES, KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_SEVERITIES, KNOWLEDGE_RESOLUTION_TYPES, KNOWLEDGE_CATEGORY_MAP, DATE_RE5, isMap2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), REF_EXT_RE, SYMBOL_REF_RE, SCHEME_RE, LINE_SUFFIX_RE, ANCHOR_RE, WALK_SKIP_DIRS, MAX_WALK_FILES = 5000, COMMENT_INTRODUCER = "(?:\\/\\/|\\/\\*|#|;|--|\\s\\*)", REMOVAL_PATH_PATTERNS, TASK_ARTIFACT_RE, SDD_DEEPLINK_RE, TEST_FILE_PATH_RE, TEST_FILE_PHRASE_RE, COMMAND_PROMPT_RE, RUNNER_RE, OUTPUT_TOKEN_RE, PLACEHOLDER_TOKEN_RE, ELLIPSIS_RE, NEGATION_RE, WORKFLOW_VERB_START_RE, PRONOUN_RE, DESCRIPTION_MAX_WORDS = 120, REQUIRED_STRATEGY_SECTIONS, ROLE_MAPPING, SHARED_FAMILIES, DEV_TRACK_PARAMS, QC_REVIEWER_PARAMS, LOAD_ORDER_HEADING_RE, FIVE_QUESTION_SECTIONS, HEADING_RE, RUNTIME_HEADING_ALIASES;
4465
+ `, GITIGNORE_PROCESS_ENTRIES, GITIGNORE_PROCESS_ENTRIES_AGENTS, DATE_PART, RFC3339_Z_RE, DATE_ONLY_RE, STATUS_WRITE_LOCKDIR = ".status-write.lockdir", LOCKDIR_HOLDER_PID = "holder.pid", heldLockDirs, BRANCH_FORMS_HINT = '"Working branch: <existing>" | "Working branch: create <new> from <base>" | "Branch policy: direct on <branch> — <reason>"', REQUIRED_FIELDS, ASSIGNMENT_ENFORCEMENT_BOLD_RE, ASSIGNMENT_ENFORCEMENT_PLAIN_RE, COMPASS_ENFORCEMENT_RE, ASSIGNMENT_BODY_START_RE, ASSIGNMENT_HEADING_RE, ASSIGNMENT_FIELD_RE, WORKFLOW_SNAPSHOT_FILE = "snapshot.json", WORKFLOW_LIFECYCLE_STATUSES, WORKFLOW_TERMINAL_STATUSES, WORKFLOW_LIFECYCLE_TYPES, DATE_RE, PLAN_STATUSES, RESIDUAL_DECISIONS, RESIDUAL_LIFECYCLES, validateStatus, DEFAULT_PROBE_TIMEOUT_MS = 1e4, QC_ALIGNMENT_FIELDS, SddScriptError, GIT_CAPTURE_MAX_BYTES, COMPASS_STATUSES, DATE_RE2, PLAN_STATUS_DONE = "Done", COMPASS_FILE = "delivery-compass.md", INDEX_README = "README.md", INDEX_HEADER = "| Iteration | Path | Description | Status |", PROJECT_ROADMAP_FILE = "roadmap.md", PROJECT_REFERENCES_DIR = "references", PROJECT_REGISTER_FILE = "residuals.json", _DEFAULT_PROJECT = "_default", ROADMAP_STATUSES, DATE_RE3, ROLLUP_FIELDS, MIGRATE_STATUS_FILE = "status.json", ARCHIVED_STATUS_V1_FILE = "archived/status.v1.json", NOTES_LEDGER_FILE = "notes.jsonl", DATE_RE4, ROOT_METADATA_LIFT_KEYS, RAW_GROUP = "__raw", isMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v), HEX_RE, OKLCH_RE, PX_RE, PLACEHOLDER_RE, TYPOGRAPHY_PROPS, REF_RE, REF_GROUPS, isPlaceholder = (value) => PLACEHOLDER_RE.test(value), PARITY_GROUPS, GRAY_STEPS, ALPHA_STEPS, ACCENT_SCALES, L3_BODY_ITEM_IDS, LEVEL_RANK, AUDIT_PRIORITIES, AUDIT_EFFORTS, AUDIT_RISKS, AUDIT_CATEGORIES, AUDIT_STATUS_FIELDS, escapeCell = (value) => value.replace(/\|/g, "\\|"), truncate = (value, max) => value.length > max ? `${value.slice(0, max)}…` : value, KNOWLEDGE_REQUIRED_FIELDS, KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_BUG_PROBLEM_TYPES, KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_SEVERITIES, KNOWLEDGE_RESOLUTION_TYPES, KNOWLEDGE_CATEGORY_MAP, DATE_RE5, isMap2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), REF_EXT_RE, SYMBOL_REF_RE, SCHEME_RE, LINE_SUFFIX_RE, ANCHOR_RE, WALK_SKIP_DIRS, MAX_WALK_FILES = 5000, COMMENT_INTRODUCER = "(?:\\/\\/|\\/\\*|#|;|--|\\s\\*)", REMOVAL_PATH_PATTERNS, TASK_ARTIFACT_RE, SDD_DEEPLINK_RE, TEST_FILE_PATH_RE, TEST_FILE_PHRASE_RE, COMMAND_PROMPT_RE, RUNNER_RE, OUTPUT_TOKEN_RE, PLACEHOLDER_TOKEN_RE, ELLIPSIS_RE, NEGATION_RE, WORKFLOW_VERB_START_RE, PRONOUN_RE, DESCRIPTION_MAX_WORDS = 120, REQUIRED_STRATEGY_SECTIONS, ROLE_MAPPING, SHARED_FAMILIES, DEV_TRACK_PARAMS, QC_REVIEWER_PARAMS, LOAD_ORDER_HEADING_RE, FIVE_QUESTION_SECTIONS, HEADING_RE, RUNTIME_HEADING_ALIASES;
4386
4466
  var init_engine = __esm(() => {
4387
4467
  SEVERITY_ORDER = ["critical", "high", "medium", "low", "nit"];
4388
4468
  CONFIG_KEYS = {
@@ -4497,20 +4577,6 @@ var init_engine = __esm(() => {
4497
4577
  "direction"
4498
4578
  ];
4499
4579
  AUDIT_STATUS_FIELDS = ["Priority", "Effort", "Risk", "Depends on", "Category", "Planned at"];
4500
- WHOLE_MATCH_PATTERNS = [
4501
- { type: "private-key", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/g },
4502
- { type: "aws-access-key", re: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
4503
- { type: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
4504
- { type: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
4505
- { type: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\.[A-Za-z0-9_-]{10,1024}\b/g },
4506
- { type: "api-secret-key", re: /\bsk-[A-Za-z0-9-]{20,}\b/g }
4507
- ];
4508
- VALUE_PATTERNS = [
4509
- {
4510
- typeOf: (key) => key.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[_-]+/g, "-"),
4511
- re: /(["']?)\b(password|passwd|api[_-]?key|access[_-]?token|auth[_-]?token|secret|token)\b(["']?)(\s*[:=]\s*)("[^"\n]{8,}"|'[^'\n]{8,}'|[A-Za-z0-9_./+\-=]{16,})/gi
4512
- }
4513
- ];
4514
4580
  KNOWLEDGE_REQUIRED_FIELDS = ["module", "date", "problem_type", "category", "severity"];
4515
4581
  KNOWLEDGE_PROBLEM_TYPES = [
4516
4582
  "build_error",
@@ -5033,7 +5099,7 @@ async function validateStatusWrite(targetPath, opts = {}) {
5033
5099
  for (const violation14 of result.violations) {
5034
5100
  const fix = violation14.fix ? ` (fix: ${violation14.fix})` : "";
5035
5101
  if (enforcement.hard) {
5036
- log("error", `${path.basename(resolved)} validation (hard gate): [${violation14.severity}] ${violation14.code}: ${violation14.message}${fix} — hardBlocked per Enforcement: hard; refusal requires a host refusal channel (skill: mstar-plan-artifacts/references/status-and-residuals.md)`);
5102
+ log("error", `${path.basename(resolved)} validation (hard gate): [${violation14.severity}] ${violation14.code}: ${violation14.message}${fix} — hardBlocked per Enforcement: hard; refusal requires a host refusal channel (skill: mstar-artifacts/references/status-and-residuals.md)`);
5037
5103
  } else {
5038
5104
  log("warn", `${path.basename(resolved)} validation: [${violation14.severity}] ${violation14.code}: ${violation14.message}${fix}`);
5039
5105
  }
@@ -5143,7 +5209,28 @@ var MorningStarHarnessPlugin = async () => {
5143
5209
  defaultStatusLogger("error", "hard-gate blocked (hardBlocked=true) — refusal requires a host refusal channel");
5144
5210
  }
5145
5211
  } else if (input.tool === "edit") {
5146
- const gate2 = await validateStatusWrite(filePath);
5212
+ classifyDirResolvers = await dirResolversLoader.load();
5213
+ if (harnessDocKindOfTarget(filePath) === null)
5214
+ return;
5215
+ const oldString = args.oldString;
5216
+ const newString = args.newString;
5217
+ const replaceAll = args.replaceAll;
5218
+ let patchedDoc;
5219
+ if (typeof oldString === "string" && oldString.length > 0 && typeof newString === "string" && (replaceAll === undefined || typeof replaceAll === "boolean")) {
5220
+ try {
5221
+ const text = fs.readFileSync(filePath, "utf8");
5222
+ const hits = text.split(oldString).length - 1;
5223
+ if (replaceAll === true || hits === 1) {
5224
+ const patched = text.split(oldString).join(newString);
5225
+ try {
5226
+ patchedDoc = JSON.parse(patched);
5227
+ } catch {
5228
+ patchedDoc = null;
5229
+ }
5230
+ }
5231
+ } catch {}
5232
+ }
5233
+ const gate2 = patchedDoc !== undefined ? await validateStatusWrite(filePath, { doc: patchedDoc }) : await validateStatusWrite(filePath);
5147
5234
  if (gate2?.hardBlocked) {
5148
5235
  defaultStatusLogger("error", "hard-gate blocked (hardBlocked=true) — refusal requires a host refusal channel");
5149
5236
  }
@@ -14,9 +14,9 @@ Run a read-only codebase audit that discovers what is worth doing and writes sel
14
14
  ## Boot
15
15
 
16
16
  1. `mstar-harness-core`
17
- 2. `mstar-audit` → SKILL.md(workflow、hard rules、scope variants、handoff 全量在此)
17
+ 2. `mstar-audit` → SKILL.md(common core:hard rules、recon、vet、variant dispatch、`## Plan output (all variants)`)+ `references/codebase-audit.md`(full-audit 变体:Phase 2 类别 + effort、scope variants、Phase 4 变体细则、audit index 模板)
18
18
  3. `mstar-roles` → `references/code-reviewer.md`(执行角色:audit 执行体)
19
- 4. `mstar-plan-conventions` (path symbols — `{PLAN_DIR}`, `{HARNESS_DIR}`)
19
+ 4. `mstar-conventions` (path symbols — `{PLAN_DIR}`, `{HARNESS_DIR}`)
20
20
  5. `mstar-host` → active host reference (invoke capability for parallel subagents)
21
21
 
22
22
  ## Routing(谁执行 audit)
@@ -33,7 +33,7 @@ The audit is **advisory** — it does not enter the per-plan state machine (`Tod
33
33
 
34
34
  ## Execute
35
35
 
36
- Execute **`mstar-audit`** end to end(SKILL.md:Recon → Audit → Vet & prioritize Write plans;effort `quick` / `standard` / `deep`;scope variants `security` / `perf` / `tests` / `branch` / `next` / `roadmap` / `simplify`)。Plans → `{PLAN_DIR}/audit-<YYYY-MM-DD>/NNN-<slug>.md` + `README.md` index,per **`mstar-plan-artifacts/references/plan-quality-bar.md`**。
36
+ Execute **`mstar-audit`** end to end(SKILL.md common core:Recon → Vet & prioritize;full-audit detail:**`references/codebase-audit.md`** —— Phase 2 九类别 fan-out、effort `quick` / `standard` / `deep`、scope variants `bare` / `quick` / `deep` / category focus(`security` / `perf` / `tests`, ...)/ `branch` / `next` / `roadmap` / `simplify`、Phase 4 变体细则、audit index 输出模板;plan writing → SKILL.md **`## Plan output (all variants)`**)。Plans → `{PLAN_DIR}/audit-<YYYY-MM-DD>/NNN-<slug>.md` + `README.md` index,per **`mstar-artifacts/references/plan-quality-bar.md`**。
37
37
 
38
38
  Executor: PM dispatches `@code-reviewer`;大型仓库 scout 扇出经 Assignment `Delegation: allowed (scout/explore only, read-only)`(Routing 表)。
39
39