@mstar-harness/dsh 3.0.1 → 3.1.1

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.
@@ -514,6 +514,14 @@ export declare function recordWorkflowVerdict(input: WorkflowVerdictInput): void
514
514
  * <id>`), never the harness root — the dir parameter IS the workflow dir
515
515
  * (the catalog passes `join(harnessDir, selection.dir)`; the writer appends
516
516
  * there via `resolveAgentFlowWriteDir`).
517
+ *
518
+ * Large-file reads (plan `20260820-dsh-ledger-tail-read`): ledgers above
519
+ * `AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES` are read as a bounded latest-first
520
+ * tail — seek from EOF, aligned to a line boundary, doubling backward until
521
+ * the window holds `limit` complete lines — so the catalog pays O(window) at
522
+ * the byte layer instead of parsing every historical line. Both paths feed
523
+ * the SAME parse funnel below (`ledgerContent` yields a `content` string),
524
+ * so tail/full parity is structural.
517
525
  * @param workflowDir - the workflow dir whose `agent-flow.jsonl` is read.
518
526
  * @param limit - explicit window bound: `undefined` → `AGENT_FLOW_DEFAULT_LIMIT`;
519
527
  * otherwise `Math.max(0, Math.floor(limit))` — `0` requests the EMPTY window.
@@ -37,10 +37,13 @@
37
37
  * re-registration, in zero-config and explicit-config deployments alike.
38
38
  * - The context provider reuses the catalog's unified machine-summary
39
39
  * source (`buildCatalogSources` — the SAME builder the engine-status
40
- * pre-step catalog row uses) and projects a BOUNDED subset: watermark +
41
- * iteration gate + compact state line. Full catalog content
42
- * (residual detail, agent-flow events, knowledge digest, branch/policy
43
- * anchors) stays out. v3 (plan `20260819-workflow-dsh-viz` Task 3): the
40
+ * pre-step catalog row uses) and projects the SLIM digest (plan
41
+ * `20260820-dsh-engine-status-slim` Task 2): the version watermark
42
+ * ALWAYS, plus ONE `workflow | plans: …` line only when the active set
43
+ * selects a lifecycle (`state.selection.kind === 'active'`). Harness dir
44
+ * and enforcement live in `mstar:harness-rules`; residuals / leases /
45
+ * direction / iteration-gate detail stay exclusive to the pre-step row.
46
+ * v3 (plan `20260819-workflow-dsh-viz` Task 3): the
44
47
  * digest reads ONLY the catalog row (`state` — itself aggregated from the
45
48
  * SELECTED workflow snapshot + project registers) — no direct
46
49
  * status.json / snapshot file reads to change. The build is TTL-memoized
package/dist/index.js CHANGED
@@ -4193,7 +4193,7 @@ import { join as join11 } from "node:path";
4193
4193
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
4194
4194
 
4195
4195
  // src/gates/agent-flow.ts
4196
- import { appendFileSync, existsSync as existsSync11, mkdirSync as mkdirSync3, readFileSync as readFileSync7, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4196
+ import { appendFileSync, closeSync, existsSync as existsSync11, mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync7, readSync, renameSync as renameSync2, rmdirSync, statSync as statSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
4197
4197
  import { join as join10 } from "node:path";
4198
4198
 
4199
4199
  // src/gates/dispatch.ts
@@ -4812,6 +4812,7 @@ var AGENT_FLOW_FILE = "agent-flow.jsonl";
4812
4812
  var AGENT_FLOW_MAX_EVENTS = 500;
4813
4813
  var AGENT_FLOW_DEFAULT_LIMIT = 50;
4814
4814
  var AGENT_FLOW_SIZE_GATE_BYTES = 64 * 1024;
4815
+ var AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES = 64 * 1024;
4815
4816
  var WORKFLOW_LEDGER_MAX_ID_LENGTH = 512;
4816
4817
  var WORKFLOW_LEDGER_MAX_LABEL_LENGTH = 512;
4817
4818
  var WORKFLOW_LEDGER_MAX_NAME_LENGTH = 1024;
@@ -5296,14 +5297,49 @@ function summaryOf(events) {
5296
5297
  return { role, outcome, count };
5297
5298
  }).sort((a, b) => b.count - a.count || a.role.localeCompare(b.role) || a.outcome.localeCompare(b.outcome));
5298
5299
  }
5300
+ function ledgerContent(file, n) {
5301
+ const size = statSync5(file).size;
5302
+ if (size <= AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES) {
5303
+ return readFileSync7(file, "utf8");
5304
+ }
5305
+ let windowBytes = AGENT_FLOW_TAIL_READ_THRESHOLD_BYTES;
5306
+ const fd = openSync(file, "r");
5307
+ try {
5308
+ for (;; ) {
5309
+ const start = Math.max(0, size - windowBytes);
5310
+ const buffer = Buffer.alloc(size - start);
5311
+ let read = 0;
5312
+ while (read < buffer.length) {
5313
+ const got = readSync(fd, buffer, read, buffer.length - read, start + read);
5314
+ if (got === 0)
5315
+ break;
5316
+ read += got;
5317
+ }
5318
+ let content = buffer.toString("utf8");
5319
+ if (start > 0) {
5320
+ const firstNewline = content.indexOf(`
5321
+ `);
5322
+ content = firstNewline >= 0 ? content.slice(firstNewline + 1) : "";
5323
+ }
5324
+ const completeLines = content.split(`
5325
+ `).length - 1;
5326
+ if (start === 0 || completeLines >= n)
5327
+ return content;
5328
+ windowBytes *= 2;
5329
+ }
5330
+ } finally {
5331
+ closeSync(fd);
5332
+ }
5333
+ }
5299
5334
  function readAgentFlow(workflowDir, limit) {
5300
5335
  const file = join10(workflowDir, AGENT_FLOW_FILE);
5301
5336
  if (!existsSync11(file)) {
5302
5337
  return { events: [], summary: [] };
5303
5338
  }
5339
+ const n = limit === undefined ? AGENT_FLOW_DEFAULT_LIMIT : Math.max(0, Math.floor(limit));
5304
5340
  let content;
5305
5341
  try {
5306
- content = readFileSync7(file, "utf8");
5342
+ content = ledgerContent(file, n);
5307
5343
  } catch {
5308
5344
  return null;
5309
5345
  }
@@ -5320,7 +5356,6 @@ function readAgentFlow(workflowDir, limit) {
5320
5356
  continue;
5321
5357
  }
5322
5358
  }
5323
- const n = limit === undefined ? AGENT_FLOW_DEFAULT_LIMIT : Math.max(0, Math.floor(limit));
5324
5359
  const latestFirst = events.reverse().slice(0, n);
5325
5360
  return {
5326
5361
  events: latestFirst.map(eventView),
@@ -5621,6 +5656,8 @@ function harnessStateSource(harnessDir) {
5621
5656
  const executionPolicy = asRecord(snapshot.execution_policy);
5622
5657
  return {
5623
5658
  selection,
5659
+ workflowType: str(snapshot.type),
5660
+ workflowStatus: str(snapshot.status),
5624
5661
  plans,
5625
5662
  residuals,
5626
5663
  residualFindings,
@@ -5643,6 +5680,8 @@ function harnessStateSource(harnessDir) {
5643
5680
  function selectionErrorState(selection, rollup, harnessDir, compass) {
5644
5681
  return {
5645
5682
  selection,
5683
+ workflowType: null,
5684
+ workflowStatus: null,
5646
5685
  plans: [],
5647
5686
  residuals: [],
5648
5687
  residualFindings: null,
@@ -8087,24 +8126,25 @@ function engineStatusProvider(ctx, resolver, bootHarnessDir) {
8087
8126
  return engineStatusSummary(entry.source);
8088
8127
  };
8089
8128
  }
8129
+ var DIGEST_PLAN_CAP = 8;
8130
+ function joinCapped(items, cap, separator, render) {
8131
+ const visible = items.slice(0, cap).map(render);
8132
+ if (items.length > cap)
8133
+ visible.push(`+${items.length - cap} more`);
8134
+ return visible.join(separator);
8135
+ }
8090
8136
  function engineStatusSummary(source) {
8091
- const lines = [
8092
- `mstar engine status: v${source.version} | harness ${stripInterpolationHazard(source.harnessDir ?? "none")} | enforcement ${source.enforcement.hard ? "hard" : "soft"}`
8093
- ];
8094
- const iteration = source.iteration;
8095
- if (iteration !== undefined) {
8096
- const gate2 = iteration.gate;
8097
- const codes = gate2.violations.map((v) => v.code).join(", ");
8098
- lines.push(`iteration ${stripInterpolationHazard(iteration.iterationId)}: gate ${gate2.ok ? "PASS" : `FAIL (${codes})`} | transition ${gate2.transition} | all plans done ${gate2.all_plans_done}`);
8099
- }
8137
+ const lines = [`mstar engine status: v${source.version}`];
8100
8138
  const state = source.state;
8101
8139
  if (state !== null) {
8102
- const plans = state.plans.length === 0 ? "none" : state.plans.map((p) => `${stripInterpolationHazard(p.id)}(${stripInterpolationHazard(p.status)})`).join(" ");
8103
- const residuals = state.residuals.length === 0 ? "none" : state.residuals.map((r) => `${r.severity} ${r.count}`).join(", ");
8104
- const leases = state.leases.length === 0 ? "none active" : state.leases.map((l) => `${stripInterpolationHazard(l.planId)} ${stripInterpolationHazard(l.holder)}`).join("; ");
8105
- lines.push(`plans: ${plans} | residuals: ${residuals} | leases: ${leases}`);
8106
- if (state.direction !== null)
8107
- lines.push(`direction: ${stripInterpolationHazard(state.direction)}`);
8140
+ const selection = state.selection;
8141
+ if (selection.kind === "active") {
8142
+ const pending = state.plans.filter((p) => p.status !== "Done");
8143
+ const plans = pending.length === 0 ? "none" : joinCapped(pending, DIGEST_PLAN_CAP, " ", (p) => `${stripInterpolationHazard(p.id)}(${stripInterpolationHazard(p.status)})`);
8144
+ const workflowType = stripInterpolationHazard(state.workflowType ?? "unknown");
8145
+ const workflowStatus = stripInterpolationHazard(state.workflowStatus ?? "unknown");
8146
+ lines.push(`workflow ${stripInterpolationHazard(selection.workflowId)} (${workflowType}) ${workflowStatus} | plans: ${plans}`);
8147
+ }
8108
8148
  }
8109
8149
  return lines.join(`
8110
8150
  `);
package/dist/types.d.ts CHANGED
@@ -199,6 +199,20 @@ export interface MstarHarnessState {
199
199
  * read).
200
200
  */
201
201
  readonly selection: WorkflowSelectionView;
202
+ /**
203
+ * The selected workflow snapshot's top-level `type` (`plan` / `iteration`),
204
+ * projected loose (`string`) like `HarnessPlanView.status`. ALWAYS-present
205
+ * nullable scalar (lossless JSON, never omitted): missing/empty → `null`;
206
+ * `null` on selection error (no snapshot read). Added for the slim
207
+ * `mstar:engine-status` digest (plan `20260820-dsh-engine-status-slim`).
208
+ */
209
+ readonly workflowType: string | null;
210
+ /**
211
+ * The selected workflow snapshot's top-level lifecycle `status`
212
+ * (`running` / `paused`; a terminal word for a stale-but-registered entry,
213
+ * decision D3). Same ALWAYS-present nullable discipline as `workflowType`.
214
+ */
215
+ readonly workflowStatus: string | null;
202
216
  /** Registered plan rows (`plan_id`/`id` + `status`), snapshot plans[] order. */
203
217
  readonly plans: readonly HarnessPlanView[];
204
218
  /** Open `residual_findings` counts by severity (non-zero only). */
@@ -15,6 +15,7 @@
15
15
  | **`{SPECS_DIR}`**(可选) | 冻结 v1-spec、ADR、program roadmap — **跨迭代长期权威** | 产品/API 规范性最高权威;**iteration-start** 由 product/architect 主写 |
16
16
  | **`{ITERATION_DIR}`**(可选) | **`<iteration-id>/` package**(`delivery-compass.md` + `guides/`、`specs/`) | Agent handoff;迭代级草稿;close 时 compound **提升** → knowledge |
17
17
  | **`{KNOWLEDGE_DIR}`**(可选) | 实现细节 SSOT、架构细则、契约说明、跨版本 tracker — **经 `mstar-compound` 结晶** | Agent handoff;**不**在 iteration-start 由 product/architect 新增 |
18
+ | **`{PROJECT_DIR}/<id>/references/`**(可选) | 主题化研究:surveys、epic roadmaps、第三方 notes — **与项目绑定**,非跨迭代 corpora | 项目所有者 / operator;engine 只列文件名,**无 markdown schema** |
18
19
  | **`{PLAN_DIR}/`** | 单 plan 主文件、durable gate summary、可选 `residuals/` | 计划执行与长期决策留档 |
19
20
 
20
21
 
@@ -43,6 +44,14 @@
43
44
  - 可选:目录级 `**{KNOWLEDGE_DIR}/AGENTS.md**` 承载命名、维护节奏、与 `{SPECS_DIR}` 的权威边界(Nexus 模式);harness 宽规则仍以 `mstar-plan-conventions` 与本 reference 为准。
44
45
  - 初始化启用知识库时:创建空表头的 `README.md`,随文档递增行。
45
46
 
47
+ ## `{PROJECT_DIR}/<id>/references/`(可选·主题化研究语料)
48
+
49
+ - **物理路径**:`**{PROJECT_DIR}/<id>/references/**`(默认 `{HARNESS_DIR}/projects/<id>/references/`;`.mstarc` `project_dir` 声明时用声明值)。
50
+ - **放什么**:**主题化研究** — surveys、epic roadmaps、第三方 notes,与拥有它的项目(`<id>`)绑定(compass ruling 1)。项目缺失用 `_default`(`{PROJECT_DIR}/_default/references/`)。
51
+ - **不放什么**:冻结规格 / ADR(→ `{SPECS_DIR}/`);compound 结晶的实现 SSOT(→ `{KNOWLEDGE_DIR}/`);迭代内 ranking notes 与草稿 spec(→ `{ITERATION_DIR}/<id>/guides|specs/`)。研究**不**迁入这三处 corpora,三处内容也**不**迁入 `references/`。
52
+ - **engine 职责边界**:`listProjectReferenceFiles(projectDir)` 只返回**文件名**(相对路径、`/` 分隔、code-unit 排序;跳过根级 `roadmap.md` / `residuals.json`;缺失目录 → `[]`);**不**读文件正文,**不**做 markdown schema 校验 — 归属语义是本 reference(技能散文)契约,不是 engine 校验。
53
+ - **历史 dump**:harness 根 `.mstar/references/` 已退役(dogfood 后缺省或仅一行 redirect);当前路径由 `mstar-project-governance` Scope 表与 `mstar-plan-conventions` `references/artifact-storage-paths.md` 命名。
54
+
46
55
  ## 文件命名
47
56
 
48
57
  - 推荐:`<topic>-<qualifier>-v<N>.md`(例:`sync-contract-gap-analysis-v1.md`),便于同主题多版共存。
@@ -74,6 +83,7 @@
74
83
  - `**{PLAN_DIR}/residuals/<plan-id>/`**:偏 **仍 open 的 R# 长文补充**(与 project register `entries[<plan-id>]` 配套,canonical 见 `mstar-plan-conventions` **SKILL.md** 开篇);见下文「open residual 散文详情」。
75
84
  - `**{KNOWLEDGE_DIR}/**`:偏 **可复用的实现向设计上下文**(架构细则、决策、分析),可被后续 plan 或多会话反复引用。
76
85
  - `**{ITERATION_DIR}/**`:偏 **某一迭代/版本** 的 package(compass + guides/specs),通常按版本索引而非按单 plan 长期复用。
86
+ - `**{PROJECT_DIR}/<id>/references/**`:偏 **主题化研究**(surveys、epic 备注、第三方 notes),随项目绑定;与 specs / knowledge / iteration guides 职责不混写。
77
87
  - review bundle、gate summary、residuals、knowledge、iterations 可互链,但职责不混写。
78
88
 
79
89
  ---
@@ -20,6 +20,7 @@
20
20
  | **workflow notes ledger** | `{HARNESS_DIR}/workflows/<id>/notes.jsonl`(gitignored;append-only 运行时笔记) | `mstar-plan-artifacts`、`mstar-iteration` |
21
21
  | **project roadmap** | `.mstar/projects/<id>/roadmap.md`(gitignored;frontmatter `{project_id, title, status, created_at, milestones[]?, residuals_ref?}` + 正文约定) | `mstar-plan-artifacts`、`mstar-iteration` |
22
22
  | **project register** | `.mstar/projects/<id>/residuals.json`(gitignored;open residual SSOT:`entries[<plan-id>]` 数组;项目缺失用 `_default`) | `mstar-plan-artifacts`、`mstar-review-qc` |
23
+ | **project references(研究语料)** | `.mstar/projects/<id>/references/`(gitignored;主题化 surveys / epic 备注 / 第三方 notes,与项目绑定;与 `{SPECS_DIR}` / `{KNOWLEDGE_DIR}` / `{ITERATION_DIR}` 不同) | `mstar-project-governance`、`mstar-plan-artifacts` |
23
24
  | **迭代 package** | `.mstar/iterations/<iteration-id>/`(gitignored;`delivery-compass.md`、`guides/`、`specs/`、可选 `README.md`) | `mstar-iteration`(读写);close 时 `mstar-compound`(提升读;默认排除 compass) |
24
25
  | **迭代索引** | `.mstar/iterations/README.md`(gitignored;一行 = 一次迭代) | `mstar-iteration`(读写) |
25
26
  | **规格** | `{HARNESS_DIR}/specs/`(默认 tracked;解析见 `mstar-plan-conventions`) | `mstar-plan-artifacts` |
@@ -19,6 +19,7 @@ description: Morning Star 项目治理层约定 —— `projects/<id>/roadmap.md
19
19
  |------|------|
20
20
  | `roadmap.md` | 项目方向与目标(frontmatter machine-checkable + body 约定) |
21
21
  | `residuals.json` | 项目 register:open residual 的 **SSOT**(`entries[<plan-id>]` 数组) |
22
+ | `references/` | 主题化研究语料(surveys / epic 备注 / 第三方 notes)。与 `{SPECS_DIR}`(冻结规格/ADR)、`{KNOWLEDGE_DIR}`(compound 结晶实现 SSOT)、`{ITERATION_DIR}`(迭代 package)**不同**;engine 只列文件名(`listProjectReferenceFiles`),**不做** markdown schema 校验 |
22
23
 
23
24
  - **`_default` 回退**:无项目流程(未指定 project id 的 plan / 单 plan / hotfix)落到 **`projects/_default/`**(engine `_DEFAULT_PROJECT`)。项目归属由 plan 的 project id 决定;未归属即 `_default`。
24
25
  - 本 skill 的 schema 事实与 **`packages/engine/src/project.ts`** 逐字一致(`validateRoadmap` / `validateProjectRegister` / `findingsCleanupGate` / `techDebtRollup`);技能文本是语义 SSOT,engine 是确定性校验。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mstar-harness/dsh",
3
- "version": "3.0.1",
3
+ "version": "3.1.1",
4
4
  "description": "Morning Star harness dsh (DeepSeek Harness) cordis function plugin — in-process engine gates (status/dispatch/lease) with hard refusal channels.",
5
5
  "keywords": [
6
6
  "dsh",