@mstar-harness/engine 3.2.6 → 3.3.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/dist/audit.d.ts CHANGED
@@ -78,6 +78,15 @@ export type ScaffoldAuditPlanOptions = {
78
78
  title: string;
79
79
  reason: string;
80
80
  }[];
81
+ needsVerification?: readonly {
82
+ lead: string;
83
+ how: string;
84
+ evidence?: string;
85
+ }[];
86
+ hardeningChecked?: readonly {
87
+ kind: "Hardening" | "Checked and clean";
88
+ text: string;
89
+ }[];
81
90
  };
82
91
  /** Result of `scaffoldAuditPlan`. `nextNumber` is the next free plan number
83
92
  * (monotonic continuation vs. any pre-existing `NNN-*.md` files). */
@@ -94,7 +103,12 @@ export type ScaffoldAuditPlanResult = {
94
103
  * directory already contains `NNN-*.md` files (same-date re-run), the new
95
104
  * batch continues after the highest existing number instead of restarting
96
105
  * at 001, and the rebuilt index includes the pre-existing plans. Rejected
97
- * findings render in the "considered and rejected" section.
106
+ * findings render in the "considered and rejected" section. Security
107
+ * dispositions (`needsVerification` / `hardeningChecked`) render in their
108
+ * documented index sections. Policy: a SUPPLIED option is the authoritative
109
+ * current set and replaces the section (resolved leads can be removed,
110
+ * revised entries updated); an OMITTED option carries the previous section
111
+ * over from the existing README so hand-added entries survive the rebuild.
98
112
  */
99
113
  export declare function scaffoldAuditPlan(outDir: string, findings: readonly AuditFinding[], options?: ScaffoldAuditPlanOptions): ScaffoldAuditPlanResult;
100
114
  /** Options for `promoteAuditPlans`. `harnessDir` is required — the snapshot
package/dist/audit.js CHANGED
@@ -113,38 +113,6 @@ var CONFIG_KEYS = {
113
113
  [MSTARC_ENFORCEMENT_KEY]: "enforcement"
114
114
  };
115
115
 
116
- // src/path.ts
117
- function assertSafePathComponent(value, what) {
118
- if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
119
- throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\") — got ${JSON.stringify(value)}`);
120
- }
121
- }
122
- var GITIGNORE_SNIPPET = `# Morning Star harness (.mstar/)
123
- # Principle: process stays local; results are shared with the team.
124
- # Default-ignore everything under .mstar/, then re-include the tracked results.
125
- .mstar/**
126
- !.mstar/AGENTS.md
127
- !.mstar/knowledge/
128
- !.mstar/knowledge/**
129
- !.mstar/specs/
130
- !.mstar/specs/**
131
- # .mstarc — repo-local harness config (may declare [config] harness_dir=<name>)
132
- .mstarc
133
- `;
134
- var GITIGNORE_SNIPPET_AGENTS = `# Morning Star harness (.agents/) — legacy
135
- # Default-ignore everything under .agents/, then re-include the tracked results.
136
- .agents/**
137
- !.agents/AGENTS.md
138
- !.agents/knowledge/
139
- !.agents/knowledge/**
140
- !.agents/specs/
141
- !.agents/specs/**
142
- `;
143
- var GITIGNORE_PROCESS_ENTRIES = GITIGNORE_SNIPPET.split(`
144
- `).filter((line) => line.startsWith(".mstar/") || line.startsWith("!.mstar/")).map((line) => line.trim());
145
- var GITIGNORE_PROCESS_ENTRIES_AGENTS = GITIGNORE_SNIPPET_AGENTS.split(`
146
- `).filter((line) => line.startsWith(".agents/") || line.startsWith("!.agents/")).map((line) => line.trim());
147
-
148
116
  // src/status.ts
149
117
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, realpathSync } from "node:fs";
150
118
  import { dirname as dirname3, join as join3, resolve as resolve3, sep } from "node:path";
@@ -336,6 +304,38 @@ function registerWorkflowEntryLocked(statusPath, entry) {
336
304
  return doc;
337
305
  }
338
306
 
307
+ // src/path.ts
308
+ function assertSafePathComponent(value, what) {
309
+ if (value === "" || value === "." || value === ".." || !/^[A-Za-z0-9._-]+$/.test(value)) {
310
+ throw new Error(`${what} must be a single safe path component ([A-Za-z0-9._-]+; not "", ".", "..", or containing "/" or "\\") — got ${JSON.stringify(value)}`);
311
+ }
312
+ }
313
+ var GITIGNORE_SNIPPET = `# Morning Star harness (.mstar/)
314
+ # Principle: process stays local; results are shared with the team.
315
+ # Default-ignore everything under .mstar/, then re-include the tracked results.
316
+ .mstar/**
317
+ !.mstar/AGENTS.md
318
+ !.mstar/knowledge/
319
+ !.mstar/knowledge/**
320
+ !.mstar/specs/
321
+ !.mstar/specs/**
322
+ # .mstarc — repo-local harness config (may declare [config] harness_dir=<name>)
323
+ .mstarc
324
+ `;
325
+ var GITIGNORE_SNIPPET_AGENTS = `# Morning Star harness (.agents/) — legacy
326
+ # Default-ignore everything under .agents/, then re-include the tracked results.
327
+ .agents/**
328
+ !.agents/AGENTS.md
329
+ !.agents/knowledge/
330
+ !.agents/knowledge/**
331
+ !.agents/specs/
332
+ !.agents/specs/**
333
+ `;
334
+ var GITIGNORE_PROCESS_ENTRIES = GITIGNORE_SNIPPET.split(`
335
+ `).filter((line) => line.startsWith(".mstar/") || line.startsWith("!.mstar/")).map((line) => line.trim());
336
+ var GITIGNORE_PROCESS_ENTRIES_AGENTS = GITIGNORE_SNIPPET_AGENTS.split(`
337
+ `).filter((line) => line.startsWith(".agents/") || line.startsWith("!.agents/")).map((line) => line.trim());
338
+
339
339
  // src/audit.ts
340
340
  function violation2(severity, code, message, fix) {
341
341
  return { ok: false, severity, code, message, fix };
@@ -515,8 +515,17 @@ function readPlanFileSummary(filePath) {
515
515
  const blocks = parseStatusBlocks(text);
516
516
  return { title: title.trim(), fields: blocks.length > 0 ? blocks[0].fields : new Map };
517
517
  }
518
+ function extractSecurityDispositionSections(text) {
519
+ const grab = (heading) => {
520
+ const match = text.match(new RegExp(`(?:^|\\r?\\n)##[ \\t]+${heading}[ \\t]*\\r?\\n(?:[ \\t]*\\r?\\n)?([\\s\\S]*?)(?=\\r?\\n## |$)`, "i"));
521
+ if (!match)
522
+ return [];
523
+ return match[1].split(/\r?\n/).map((line) => line.replace(/\r$/, "")).filter((line) => line.startsWith("- "));
524
+ };
525
+ return { needsVerification: grab("Needs verification"), hardeningChecked: grab("Hardening & checked notes") };
526
+ }
518
527
  function renderIndex(params) {
519
- const { date, repoName, repoShortSha, rows, rejected } = params;
528
+ const { date, repoName, repoShortSha, rows, rejected, needsVerification, hardeningChecked } = params;
520
529
  const findingsRows = rows.map((r) => `| ${r.num} | ${escapeCell(r.title)} | ${r.category} | ${escapeCell(truncate(r.impact, 80))} | ${r.effort} | ${r.risk} | ${r.confidence} | ${escapeCell(truncate(r.evidence, 80))} |`).join(`
521
530
  `);
522
531
  const directionRows = rows.filter((r) => r.category === "direction").map((r) => `- ${escapeCell(r.title)} — ${escapeCell(truncate(r.impact, 120))}`).join(`
@@ -537,6 +546,12 @@ function renderIndex(params) {
537
546
  if (directionRows !== "") {
538
547
  sections.push("", "## Direction", "", directionRows);
539
548
  }
549
+ if (needsVerification.length > 0) {
550
+ sections.push("", "## Needs verification", "", ...needsVerification);
551
+ }
552
+ if (hardeningChecked.length > 0) {
553
+ sections.push("", "## Hardening & checked notes", "", ...hardeningChecked);
554
+ }
540
555
  sections.push("", "## Execution order & status", "", "| Plan | Title | Priority | Effort | Depends on | Status |", "|------|-------|----------|--------|------------|--------|", executionRows);
541
556
  if (rejectedRows !== "") {
542
557
  sections.push("", "## Findings considered and rejected", "", rejectedRows);
@@ -550,6 +565,8 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
550
565
  const date = options.date ?? new Date().toISOString().slice(0, 10);
551
566
  const plannedAt = options.plannedAt ?? { commit: options.repoShortSha ?? "unknown", date };
552
567
  mkdirSync3(outDir, { recursive: true });
568
+ const existingReadme = join4(outDir, "README.md");
569
+ const carried = existsSync3(existingReadme) ? extractSecurityDispositionSections(readFileSync3(existingReadme, "utf8")) : { needsVerification: [], hardeningChecked: [] };
553
570
  const existing = readdirSync2(outDir).filter((f) => /^\d{3}-.*\.md$/.test(f));
554
571
  let next = existing.reduce((max, f) => Math.max(max, Number(f.slice(0, 3))), 0) + 1;
555
572
  const written = [];
@@ -603,12 +620,16 @@ function scaffoldAuditPlan(outDir, findings, options = {}) {
603
620
  row.dependsOn = finding.dependsOn ?? "none";
604
621
  }
605
622
  });
623
+ const needsVerificationLines = options.needsVerification !== undefined ? options.needsVerification.map((nv) => `- ${escapeCell(nv.lead)}: ${escapeCell(nv.how)}${nv.evidence ? ` (${escapeCell(nv.evidence)})` : ""}`) : carried.needsVerification;
624
+ const hardeningCheckedLines = options.hardeningChecked !== undefined ? options.hardeningChecked.map((hc) => `- ${hc.kind}: ${escapeCell(hc.text)}`) : carried.hardeningChecked;
606
625
  writeFileSync3(join4(outDir, "README.md"), renderIndex({
607
626
  date,
608
627
  repoName: options.repoName ?? "repo",
609
628
  repoShortSha: options.repoShortSha ?? "unknown",
610
629
  rows,
611
- rejected: options.rejected ?? []
630
+ rejected: options.rejected ?? [],
631
+ needsVerification: needsVerificationLines,
632
+ hardeningChecked: hardeningCheckedLines
612
633
  }));
613
634
  return { outDir: resolve4(outDir), date, files: written, nextNumber: next };
614
635
  }