@mstar-harness/engine 3.2.5 → 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 +15 -1
- package/dist/audit.js +55 -34
- package/dist/engine.js +1366 -1300
- package/dist/index.d.ts +1 -1
- package/dist/path.d.ts +31 -5
- package/package.json +1 -1
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
|
}
|