@mstar-harness/engine 2.2.0 → 2.4.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/engine.js CHANGED
@@ -674,7 +674,7 @@ function parseAssignmentBranchForms(assignmentText) {
674
674
  if (fields.branchPolicy !== undefined && fields.branchPolicy !== "") {
675
675
  const direct = fields.branchPolicy.match(/^direct\s+on\s+(\S+)/i);
676
676
  if (direct) {
677
- const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[—–]|--|-)\s*(.+))?$/);
677
+ const strict = fields.branchPolicy.match(/^direct\s+on\s+(\S+)(?:\s*(?:[\u2014\u2013]|--|-)\s*(.+))?$/);
678
678
  forms.directOn = { branch: direct[1].trim(), reason: strict ? (strict[2] ?? "").trim() : "" };
679
679
  }
680
680
  }
@@ -2552,6 +2552,7 @@ function renderIndex(params) {
2552
2552
  if (rejectedRows !== "") {
2553
2553
  sections.push("", "## Findings considered and rejected", "", rejectedRows);
2554
2554
  }
2555
+ sections.push("", "## Red-team dispositions", "", "- <finding>: <survived / refuted / hallucination-dropped / uncovered-kept>, <one-line reason>");
2555
2556
  return `${sections.join(`
2556
2557
  `)}
2557
2558
  `;
@@ -3080,11 +3081,31 @@ function findTemporaryMarkers(fileText) {
3080
3081
  }
3081
3082
  return { ok: violations.length === 0, violations, markers };
3082
3083
  }
3084
+ var TASK_ARTIFACT_RE = /\btask-\d+(?:-(?:brief|report|fix-report|diff)|\.diff)\b/g;
3085
+ var SDD_DEEPLINK_RE = /\.(?:mstar|agents)\/sdd\/([^\s/<>{}\[\]"'\*\?]+)/g;
3086
+ function findEphemeralCitations(skillText) {
3087
+ const citations = [];
3088
+ const lines = skillText.split(/\r?\n/);
3089
+ for (let i = 0;i < lines.length; i++) {
3090
+ const found = [];
3091
+ for (const m of lines[i].matchAll(TASK_ARTIFACT_RE)) {
3092
+ found.push({ index: m.index, match: m[0], kind: "task-artifact" });
3093
+ }
3094
+ for (const m of lines[i].matchAll(SDD_DEEPLINK_RE)) {
3095
+ found.push({ index: m.index, match: m[0], kind: "sdd-deeplink" });
3096
+ }
3097
+ found.sort((a, b) => a.index - b.index);
3098
+ for (const f of found) {
3099
+ citations.push({ line: i + 1, match: f.match, kind: f.kind });
3100
+ }
3101
+ }
3102
+ return citations;
3103
+ }
3083
3104
  var TEST_FILE_PATH_RE = /[\w./-]+\.(?:test|spec)\.[a-z0-9]+/i;
3084
3105
  var TEST_FILE_PHRASE_RE = /\btest files?\b/i;
3085
3106
  var COMMAND_PROMPT_RE = /^\s*[$>]\s*\S/;
3086
3107
  var RUNNER_RE = /\b(?:bun|pnpm|npm|yarn|npx|bunx)\s+(?:test|run|exec)\b|\b(?:npx|bunx)\s+[\w./-]+\b|\b(?:tsc|vitest|jest|mocha|pytest)\b|\bgo\s+test\b|\bcargo\s+test\b/i;
3087
- var OUTPUT_TOKEN_RE = /[✓✔✗✘]|\b(?:PASS|FAIL)\b|\b\d+\s+(?:pass(?:es|ed)?|fail(?:s|ed|ing)?|skipped|tests?|ok)\b|\bok\s+\d+\b|\ball\s+ok\b|exit(?:ed)?\s+(?:with\s+)?(?:code\s+)?\d+/i;
3108
+ var OUTPUT_TOKEN_RE = /[\u2713\u2714\u2717\u2718]|\b(?:PASS|FAIL)\b|\b\d+\s+(?:pass(?:es|ed)?|fail(?:s|ed|ing)?|skipped|tests?|ok)\b|\bok\s+\d+\b|\ball\s+ok\b|exit(?:ed)?\s+(?:with\s+)?(?:code\s+)?\d+/i;
3088
3109
  function assertSddTddTriple(reportText) {
3089
3110
  const violations = [];
3090
3111
  const lines = reportText.split(/\r?\n/);
@@ -3447,13 +3468,56 @@ var FIVE_QUESTION_SECTIONS = [
3447
3468
  { key: "evidence", label: "Evidence", question: "what a correct result looks like (success criteria / evidence)" },
3448
3469
  { key: "references", label: "References", question: "additional resources to open when the main path is not enough" }
3449
3470
  ];
3471
+ function stripFrontmatter(text) {
3472
+ const lines = text.replace(/^\uFEFF/, "").split(/\r?\n/);
3473
+ if (lines.length === 0 || lines[0].trim() !== "---")
3474
+ return text;
3475
+ for (let i = 1;i < lines.length; i++) {
3476
+ if (lines[i].trim() === "---")
3477
+ return lines.slice(i + 1).join(`
3478
+ `);
3479
+ }
3480
+ return text;
3481
+ }
3450
3482
  var HEADING_RE = /^#{1,6}\s+[^\r\n]+$/;
3451
- function lintFiveQuestion(bodyText) {
3452
- const headings = bodyText.split(/\r?\n/).filter((line) => HEADING_RE.test(line)).map((line) => line.replace(/^#{1,6}\s+/, "").trim().toLowerCase());
3483
+ function collectHeadings(bodyText) {
3484
+ const headings = [];
3485
+ let inFence = false;
3486
+ for (const line of bodyText.split(/\r?\n/)) {
3487
+ if (line.startsWith("```") || line.startsWith("~~~")) {
3488
+ inFence = !inFence;
3489
+ continue;
3490
+ }
3491
+ if (!inFence && HEADING_RE.test(line)) {
3492
+ headings.push(line.replace(/^#{1,6}\s+/, "").trim().toLowerCase());
3493
+ }
3494
+ }
3495
+ return headings;
3496
+ }
3497
+ var RUNTIME_HEADING_ALIASES = {
3498
+ workflow: ["process", "playbook"],
3499
+ "decision-rules": [
3500
+ "hard rules",
3501
+ "core rules",
3502
+ "rule",
3503
+ "gate",
3504
+ "not to do",
3505
+ "red flags",
3506
+ "反模式",
3507
+ "红线",
3508
+ "规则",
3509
+ "门禁"
3510
+ ],
3511
+ evidence: ["output format", "证据"],
3512
+ references: ["dependencies", "关系"]
3513
+ };
3514
+ function lintFiveQuestion(bodyText, mode = "authoring") {
3515
+ const headings = collectHeadings(bodyText);
3453
3516
  const violations = [];
3454
3517
  for (const section of FIVE_QUESTION_SECTIONS) {
3455
3518
  const label = section.label.toLowerCase();
3456
- const covered = headings.some((heading) => heading.includes(label));
3519
+ const aliases = mode === "runtime" ? RUNTIME_HEADING_ALIASES[section.key] ?? [] : [];
3520
+ const covered = headings.some((heading) => heading.includes(label) || aliases.some((alias) => heading.includes(alias)));
3457
3521
  if (!covered) {
3458
3522
  violations.push(violation11("low", `skill-authoring.five-question.${section.key}`, `body does not answer "${section.question}" — no "${section.label}" section (mstar-skill-authoring § Body 必须回答的 5 问 / § 默认 Body 结构)`, `add a "## ${section.label}" section covering ${section.question}`));
3459
3523
  }
@@ -3482,6 +3546,7 @@ export {
3482
3546
  techDebtRollup,
3483
3547
  taskReportExists,
3484
3548
  taskBrief,
3549
+ stripFrontmatter,
3485
3550
  singleReviewSnapshot,
3486
3551
  sddWorkspace,
3487
3552
  scopeGuard,
@@ -3526,6 +3591,7 @@ export {
3526
3591
  findingsCleanupGate,
3527
3592
  findTemporaryMarkers,
3528
3593
  findSimplifyMarkers,
3594
+ findEphemeralCitations,
3529
3595
  executionModeToN,
3530
3596
  evaluatePhaseGate,
3531
3597
  emitGitignoreSnippet,
@@ -3553,6 +3619,7 @@ export {
3553
3619
  SddScriptError,
3554
3620
  SHARED_FAMILIES,
3555
3621
  SEVERITY_ORDER,
3622
+ RUNTIME_HEADING_ALIASES,
3556
3623
  ROLE_MAPPING,
3557
3624
  QC_REVIEWER_PARAMS,
3558
3625
  KNOWLEDGE_SEVERITIES,
package/dist/index.d.ts CHANGED
@@ -42,11 +42,11 @@ export type { AuditCategory, AuditEffort, AuditFinding, AuditPriority, AuditRisk
42
42
  export { AUDIT_CATEGORIES, AUDIT_EFFORTS, AUDIT_PRIORITIES, AUDIT_RISKS, redactSecrets, scaffoldAuditPlan, validateAuditStatusBlocks, } from "./audit.js";
43
43
  export type { ReferenceCheckResult } from "./compound.js";
44
44
  export { KNOWLEDGE_BUG_PROBLEM_TYPES, KNOWLEDGE_CATEGORY_MAP, KNOWLEDGE_KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_PROBLEM_TYPES, KNOWLEDGE_REQUIRED_FIELDS, KNOWLEDGE_RESOLUTION_TYPES, KNOWLEDGE_SEVERITIES, assertIndexRows, compoundRefreshScope, referenceExists, scopeGuard, validateSchemaYaml, } from "./compound.js";
45
- export type { PlanQualityFinding, PlanQualityResult, SimplifyMarker, TemporaryMarker, TemporaryMarkerResult, } from "./lint.js";
46
- export { assertSddTddTriple, findSimplifyMarkers, findTemporaryMarkers, lintSkillFrontmatter, lintStrategySections, planQualityBar, } from "./lint.js";
45
+ export type { EphemeralCitation, PlanQualityFinding, PlanQualityResult, SimplifyMarker, TemporaryMarker, TemporaryMarkerResult, } from "./lint.js";
46
+ export { assertSddTddTriple, findEphemeralCitations, findSimplifyMarkers, findTemporaryMarkers, lintSkillFrontmatter, lintStrategySections, planQualityBar, } from "./lint.js";
47
47
  export type { DevTrackParam, QcReviewerParam, RoleFamily, RoleMappingEntry, RoleMappingOptions, } from "./roles.js";
48
48
  export { DEV_TRACK_PARAMS, QC_REVIEWER_PARAMS, ROLE_MAPPING, SHARED_FAMILIES, lintLoadOrder, validateRoleMapping, } from "./roles.js";
49
49
  export type { DetectResult, HostAdapter, HostId, SkillRootPaths, ToolSignal } from "./host.js";
50
50
  export { detectHost, resolveSkillRoot } from "./host.js";
51
- export type { FiveQuestionSection } from "./skill-authoring.js";
52
- export { FIVE_QUESTION_SECTIONS, lintFiveQuestion, lintFrontmatter, resolveAssetPath, } from "./skill-authoring.js";
51
+ export type { FiveQuestionMode, FiveQuestionSection } from "./skill-authoring.js";
52
+ export { FIVE_QUESTION_SECTIONS, RUNTIME_HEADING_ALIASES, lintFiveQuestion, lintFrontmatter, resolveAssetPath, stripFrontmatter, } from "./skill-authoring.js";
package/dist/lint.d.ts CHANGED
@@ -27,6 +27,10 @@
27
27
  * contract (not a workflow summary), third person.
28
28
  * - STRATEGY.md structure: `mstar-strategy` SKILL.md § STRATEGY.md structure —
29
29
  * six required sections.
30
+ * - Ephemeral citations: knowledge `conventions/skill-content-porting-discipline.md`
31
+ * §3 ("No ephemeral citations in durable skill text") + session evaluation
32
+ * 2026-08-16 discrimination contract — concrete task-artifact references
33
+ * and SDD deeplinks are ephemeral; placeholder forms are not.
30
34
  *
31
35
  * Enforcement depth: roadmap §8.5 C4 — v1 lints are non-blocking
32
36
  * `ValidationResult`s; callers surface them as warnings.
@@ -95,6 +99,45 @@ export type TemporaryMarkerResult = GateResult & {
95
99
  * carry); do not special-case it without a real FP report.
96
100
  */
97
101
  export declare function findTemporaryMarkers(fileText: string): TemporaryMarkerResult;
102
+ /**
103
+ * An ephemeral citation found in durable skill text: a concrete reference to
104
+ * a per-task artifact or an SDD deeplink that survives nothing (knowledge
105
+ * conventions/skill-content-porting-discipline.md §3 — "No ephemeral
106
+ * citations in durable skill text": a calibration line citing an SDD task
107
+ * report violates standalone + survives nothing; instances/examples cite
108
+ * in-repo artifacts only).
109
+ */
110
+ export type EphemeralCitation = {
111
+ /** 1-based line number of the citation. */
112
+ line: number;
113
+ /** The matched citation token (artifact name or deeplink prefix). */
114
+ match: string;
115
+ /** `task-artifact`: `task-<digits>-(brief|report|fix-report|diff)`;
116
+ * `sdd-deeplink`: `.mstar/sdd/` / `.agents/sdd/` + a concrete first
117
+ * segment. */
118
+ kind: "task-artifact" | "sdd-deeplink";
119
+ };
120
+ /**
121
+ * Find ephemeral citations in skill text (knowledge
122
+ * conventions/skill-content-porting-discipline.md §3 + session evaluation
123
+ * 2026-08-16 discrimination contract).
124
+ *
125
+ * Discrimination (HARD — zero false positives on the skills corpus):
126
+ * - `task-<digits>-(brief|report|fix-report|diff)` with 1+ digits is a
127
+ * concrete instance → reported (`task-2-report`, `task-1.diff`).
128
+ * Placeholders (`task-N-brief`, `task-N-report`, `<plan-id>`,
129
+ * `{SDD_DIR}/task-N-report.md`) never match.
130
+ * - `.mstar/sdd/<segment>` / `.agents/sdd/<segment>` with a concrete first
131
+ * segment (`20260815-x`) → reported; `<plan-id>` / `{SDD_DIR}` segments
132
+ * are template forms → never match.
133
+ *
134
+ * Discovery only — a finder returning an array, same shape as
135
+ * `findSimplifyMarkers`, NOT a GateResult; callers wrap findings into
136
+ * `ViolationResult`s (codes `skill.ephemeral.task-artifact` /
137
+ * `skill.ephemeral.sdd-deeplink`). Citations are reported line by line in
138
+ * 1-based line order, source order within a line.
139
+ */
140
+ export declare function findEphemeralCitations(skillText: string): EphemeralCitation[];
98
141
  /**
99
142
  * Assert the SDD TDD triple is present in a `task-N-report.md` text
100
143
  * (mstar-coding-behavior § Integration Notes — "completion evidence must
@@ -36,19 +36,43 @@ export type FiveQuestionSection = {
36
36
  question: string;
37
37
  };
38
38
  export declare const FIVE_QUESTION_SECTIONS: readonly FiveQuestionSection[];
39
+ /** Strip a leading `---`-fenced YAML frontmatter block, returning the body
40
+ * (the five-question lint takes the body; the frontmatter lint takes the
41
+ * full doc). Returns the input unchanged when there is no closing fence —
42
+ * an unparseable block is treated as body rather than destroyed. Canonical
43
+ * helper shared by the CLI, drift-lint Guard 5 and the engine corpus test. */
44
+ export declare function stripFrontmatter(text: string): string;
45
+ /** Lint mode: `authoring` (canonical headings only — greenfield / strict)
46
+ * or `runtime` (canonical plus the locked `RUNTIME_HEADING_ALIASES` table
47
+ * — shipped `mstar-*` topic skills). */
48
+ export type FiveQuestionMode = "authoring" | "runtime";
49
+ /**
50
+ * Locked runtime alias table (plan 20260816-audit-001-five-question-runtime-
51
+ * alignment, Step 2): heading synonyms that answer the same question for
52
+ * shipped topic skills, verified against the corpus at `81480e7`. Tokens are
53
+ * case-insensitive heading substrings (any heading level); the `decision-rules`
54
+ * breadth is bounded by the corpus regression test pinning current state.
55
+ * `load-order` has no aliases — the canonical label covers 15/16 skills and
56
+ * `mstar-host` closes its gap with a corpus edit instead. Chinese tokens are
57
+ * `\uXXXX` escapes (ASCII-only src literals; runtime value is identical).
58
+ */
59
+ export declare const RUNTIME_HEADING_ALIASES: Readonly<Record<string, readonly string[]>>;
39
60
  /**
40
61
  * Lint a SKILL.md body for the 5-question contract (mstar-skill-authoring
41
62
  * § Body 必须回答的 5 问). Heuristic: each of the five questions must be
42
63
  * answered by the presence of its canonical section heading (case-
43
64
  * insensitive substring match on heading text, any heading level — so
44
65
  * "## Load Order (Required)" and "### Workflow — main path" both match).
45
- * Content judgment (whether the answer is actually narrow / procedural)
46
- * stays prompt. Advisory: violations are `low` severity (v1 non-blocking).
66
+ * `mode: "runtime"` additionally accepts the locked `RUNTIME_HEADING_ALIASES`
67
+ * synonyms for shipped topic skills; `authoring` (default) stays canonical-
68
+ * only — greenfield skills still must use the canonical headings. Content
69
+ * judgment (whether the answer is actually narrow / procedural) stays prompt.
70
+ * Advisory: violations are `low` severity (v1 non-blocking).
47
71
  *
48
72
  * Violations: `skill-authoring.five-question.<key>` for each uncovered
49
73
  * question.
50
74
  */
51
- export declare function lintFiveQuestion(bodyText: string): GateResult;
75
+ export declare function lintFiveQuestion(bodyText: string, mode?: FiveQuestionMode): GateResult;
52
76
  /**
53
77
  * Resolve a skill-relative asset path (mstar-skill-authoring § Skill-
54
78
  * relative script and asset paths: name assets as skill `<name>` →
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/engine",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "Morning Star Harness Workflow Engine — deterministic workflow enforcement library (path, status, lease, dispatch, sdd, iteration, lint gates).",
5
5
  "license": "MIT",
6
6
  "repository": {