dsh-gate-agent 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -153,6 +153,36 @@ Host 插件:用 phase/node 门禁组织讨论、规划、实施、评审、验
153
153
 
154
154
  从 `discuss` 开始,不进入 planning/implement。
155
155
 
156
+ 初始化会生成:
157
+
158
+ - `.agents/gate/verify-notes.mjs`
159
+ - `.agents/gate/ci/*.ts`
160
+ - `.agents/gate/artifact-policy.json`
161
+ - `.agents/gate/init-manifest.json`
162
+
163
+ `init-manifest.json` 记录生成物 hash、artifact-policy hash、AGENTS managed block、verify-notes script 和 notes 目录结构。它被 git 跟踪;同内容重跑 init 时字节稳定。
164
+
165
+ 只读核对:
166
+
167
+ ```text
168
+ gate_init_verify
169
+ ```
170
+
171
+ 它会检查:
172
+
173
+ - 5 个 CI 脚本 + launcher 的 SHA-256;
174
+ - `artifact-policy.json` 的 schema;用户修改后有效则显示 `customized`;
175
+ - AGENTS managed block;
176
+ - `scripts.verify-notes`;
177
+ - workflow 是否存在;
178
+ - notes 四目录结构。
179
+
180
+ 不检查 Note 内容;Note 内容继续用:
181
+
182
+ ```text
183
+ npm run verify-notes
184
+ ```
185
+
156
186
  ### Planning
157
187
 
158
188
  ```text
@@ -315,6 +345,20 @@ gate_status()
315
345
 
316
346
  root-only;在 discuss 或 `orchestrating/plan` 派 research worker。worker 只能通过 artifact policy 写 research 文档,不获得通用 workspace 写。
317
347
 
348
+ ### gate_init_verify
349
+
350
+ root-only 只读校验 `/gate-init` 产物:
351
+
352
+ - `.agents/gate/init-manifest.json` 是否存在且有效;
353
+ - 5 个 CI 脚本 + `verify-notes.mjs` 的 hash;
354
+ - `artifact-policy.json` schema(custom 有效也通过);
355
+ - AGENTS managed block;
356
+ - `scripts.verify-notes`;
357
+ - workflow 存在性;
358
+ - notes 四目录。
359
+
360
+ 不修改文件,不运行 `verify-notes`;Note 内容校验仍用 `npm run verify-notes`。
361
+
318
362
  ### Note 工具
319
363
 
320
364
  - `gate_note_write`:planning worker 写 proposed Note,或原地更新 implemented Note;通用 `write`/`edit`/`apply_patch` 和仓库写被拒绝,只读 shell 可用。
@@ -527,6 +571,27 @@ discuss 和 plan 默认不允许普通文件写。`handoff`、PRD、research 等
527
571
 
528
572
  `reviewLimit` 是每个 review 节点的最大轮次;默认 5,范围 1–20。达到上限仍有 findings 时进入 blocked。
529
573
 
574
+ 它**不是项目文件**,而是 DSH profile 的插件 Config。当前 profile 的覆盖文件是:
575
+
576
+ ```text
577
+ %USERPROFILE%\.dsh\profiles\<profile>\cordis.patch.yml
578
+ ```
579
+
580
+ 在 profile patch 中按 bundle 的 `id: gate` 覆盖:
581
+
582
+ ```yaml
583
+ - id: gate
584
+ config:
585
+ reviewLimit: 8
586
+ ```
587
+
588
+ - 没有 `gate` 条目时使用默认值 5;
589
+ - 修改后重启 DSH 才生效;
590
+ - 不要修改 `packages/gate/cordis.patch.yml` 来配置已有 profile;那是 bundle 默认层,profile patch 会覆盖它;
591
+ - 如果使用自定义 Agent Preset,配置应放在该 preset 中对应的 `dsh-gate-agent` 条目。
592
+
593
+ `.agents/gate/artifact-policy.json` 是项目级 artifact 白名单,与这里的插件 Config 是两套配置。
594
+
530
595
 
531
596
  ## 使用边界
532
597
 
package/lib/index.js CHANGED
@@ -1365,7 +1365,7 @@ function installRestrictions(ctx, deps) {
1365
1365
  }
1366
1366
 
1367
1367
  // src/commands.ts
1368
- import path10 from "node:path";
1368
+ import path11 from "node:path";
1369
1369
 
1370
1370
  // src/notes/provision.ts
1371
1371
  import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "node:fs";
@@ -1621,9 +1621,188 @@ function writeArtifact(root, requested, content) {
1621
1621
  return { relative: path9.relative(root, target).replaceAll("\\", "/") };
1622
1622
  }
1623
1623
 
1624
+ // src/init-manifest.ts
1625
+ import { createHash } from "node:crypto";
1626
+ import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "node:fs";
1627
+ import path10 from "node:path";
1628
+ var INIT_MANIFEST_PATH = ".agents/gate/init-manifest.json";
1629
+ var ARTIFACT_POLICY_PATH2 = ".agents/gate/artifact-policy.json";
1630
+ var AGENTS_BLOCK = /<!-- gate-notes:start -->[\s\S]*?<!-- gate-notes:end -->/u;
1631
+ function sha256Bytes(value) {
1632
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
1633
+ }
1634
+ function sha256File(file) {
1635
+ try {
1636
+ return sha256Bytes(readFileSync4(file));
1637
+ } catch {
1638
+ return null;
1639
+ }
1640
+ }
1641
+ function packageVersion() {
1642
+ try {
1643
+ const pkg = JSON.parse(readFileSync4(path10.join(PACKAGE_ROOT, "package.json"), "utf8"));
1644
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
1645
+ } catch {
1646
+ return "unknown";
1647
+ }
1648
+ }
1649
+ function currentAgentsBlock(root) {
1650
+ try {
1651
+ const text = readFileSync4(path10.join(root, "AGENTS.md"), "utf8");
1652
+ return AGENTS_BLOCK.exec(text)?.[0] ?? null;
1653
+ } catch {
1654
+ return null;
1655
+ }
1656
+ }
1657
+ function readManifest(root) {
1658
+ try {
1659
+ const value = JSON.parse(readFileSync4(path10.join(root, INIT_MANIFEST_PATH), "utf8"));
1660
+ if (value === null || typeof value !== "object" || value.version !== 1) return null;
1661
+ if (typeof value.createdAt !== "string" || typeof value.gateVersion !== "string") return null;
1662
+ if (value.immutable === null || typeof value.immutable !== "object" || Array.isArray(value.immutable)) return null;
1663
+ if (value.managed === null || typeof value.managed !== "object" || Array.isArray(value.managed)) return null;
1664
+ if (value.notes === null || typeof value.notes !== "object" || Array.isArray(value.notes)) return null;
1665
+ return value;
1666
+ } catch {
1667
+ return null;
1668
+ }
1669
+ }
1670
+ function immutablePaths() {
1671
+ return [
1672
+ ...NOTES_CI_SCRIPTS.map((name) => `.agents/gate/ci/${name}`),
1673
+ ".agents/gate/verify-notes.mjs"
1674
+ ];
1675
+ }
1676
+ function provisionInitManifest(root, packageScript) {
1677
+ const previous = readManifest(root);
1678
+ const createdAt = typeof previous?.createdAt === "string" ? previous.createdAt : (/* @__PURE__ */ new Date()).toISOString();
1679
+ const immutable = {};
1680
+ for (const relative of immutablePaths()) {
1681
+ const hash = sha256File(path10.join(root, relative));
1682
+ if (hash !== null) immutable[relative] = hash;
1683
+ }
1684
+ const policyFile = path10.join(root, ARTIFACT_POLICY_PATH2);
1685
+ const policyHash = sha256File(policyFile) ?? sha256Bytes("");
1686
+ const block = currentAgentsBlock(root);
1687
+ let pkgScript = null;
1688
+ try {
1689
+ const pkg = JSON.parse(readFileSync4(path10.join(root, "package.json"), "utf8"));
1690
+ pkgScript = typeof pkg.scripts?.["verify-notes"] === "string" ? pkg.scripts["verify-notes"] : null;
1691
+ } catch {
1692
+ }
1693
+ const manifest = {
1694
+ version: 1,
1695
+ gateVersion: packageVersion(),
1696
+ createdAt,
1697
+ immutable,
1698
+ managed: {
1699
+ artifactPolicy: { path: ARTIFACT_POLICY_PATH2, schemaVersion: 1, sha256: policyHash },
1700
+ agentsBlockHash: block === null ? null : sha256Bytes(block),
1701
+ packageScript: pkgScript ?? packageScript,
1702
+ workflow: ".github/workflows/verify-notes.yml"
1703
+ },
1704
+ notes: { path: ".agents/notes", lifecycles: ["proposed", "implemented", "rejected", "archived"] }
1705
+ };
1706
+ mkdirSync5(path10.dirname(path10.join(root, INIT_MANIFEST_PATH)), { recursive: true });
1707
+ writeFileSync5(path10.join(root, INIT_MANIFEST_PATH), `${JSON.stringify(manifest, null, 2)}
1708
+ `);
1709
+ return { path: INIT_MANIFEST_PATH, manifest, status: previous === null ? "created" : "updated" };
1710
+ }
1711
+ function verifyInit(root) {
1712
+ const manifest = readManifest(root);
1713
+ const issues = [];
1714
+ const immutable = [];
1715
+ for (const relative of immutablePaths()) {
1716
+ const actual = sha256File(path10.join(root, relative));
1717
+ const expected = manifest?.immutable?.[relative];
1718
+ if (actual === null) {
1719
+ immutable.push({ path: relative, status: manifest === null ? "unknown" : "missing" });
1720
+ if (manifest !== null) issues.push({ severity: "error", code: "immutable-missing", message: `${relative} \u7F3A\u5931` });
1721
+ } else if (manifest === null) {
1722
+ immutable.push({ path: relative, status: "unknown" });
1723
+ } else if (expected === void 0) {
1724
+ immutable.push({ path: relative, status: "missing" });
1725
+ issues.push({ severity: "error", code: "immutable-manifest-missing", message: `${relative} \u4E0D\u5728 init-manifest.immutable` });
1726
+ } else if (actual !== expected) {
1727
+ immutable.push({ path: relative, status: "modified" });
1728
+ issues.push({ severity: "error", code: "immutable-modified", message: `${relative} \u4E0E init-manifest \u4E0D\u4E00\u81F4` });
1729
+ } else {
1730
+ immutable.push({ path: relative, status: "ok" });
1731
+ }
1732
+ }
1733
+ const manifestExists = existsSync5(path10.join(root, INIT_MANIFEST_PATH));
1734
+ if (manifest === null) issues.push({ severity: "error", code: manifestExists ? "manifest-invalid" : "manifest-missing", message: manifestExists ? "init-manifest \u65E0\u6548\uFF1B\u91CD\u65B0\u8FD0\u884C /gate-init" : "init-manifest \u7F3A\u5931\uFF1B\u91CD\u65B0\u8FD0\u884C /gate-init" });
1735
+ let artifactPolicy = "missing";
1736
+ const policyFile = path10.join(root, ARTIFACT_POLICY_PATH2);
1737
+ if (existsSync5(policyFile)) {
1738
+ try {
1739
+ const parsed = parseArtifactPolicy(JSON.parse(readFileSync4(policyFile, "utf8")));
1740
+ if (parsed.policy === void 0) artifactPolicy = "invalid";
1741
+ else {
1742
+ const actualHash = sha256File(policyFile);
1743
+ artifactPolicy = manifest?.managed?.artifactPolicy?.sha256 !== void 0 && actualHash !== manifest.managed.artifactPolicy.sha256 ? "customized" : "ok";
1744
+ }
1745
+ } catch {
1746
+ artifactPolicy = "invalid";
1747
+ }
1748
+ }
1749
+ if (artifactPolicy === "missing") issues.push({ severity: "error", code: "artifact-policy-missing", message: "artifact-policy.json \u7F3A\u5931" });
1750
+ if (artifactPolicy === "invalid") issues.push({ severity: "error", code: "artifact-policy-invalid", message: "artifact-policy.json \u65E0\u6548" });
1751
+ const currentBlock = currentAgentsBlock(root);
1752
+ let agentsBlock = manifest === null ? "unknown" : "missing";
1753
+ if (currentBlock !== null && manifest !== null) {
1754
+ agentsBlock = manifest.managed?.agentsBlockHash !== null && manifest.managed?.agentsBlockHash !== void 0 && sha256Bytes(currentBlock) !== manifest.managed.agentsBlockHash ? "modified" : "ok";
1755
+ }
1756
+ if (agentsBlock === "missing") issues.push({ severity: "error", code: "agents-block-missing", message: "AGENTS.md \u7F3A\u5C11 gate managed block" });
1757
+ if (agentsBlock === "modified") issues.push({ severity: "warning", code: "agents-block-modified", message: "AGENTS.md managed block \u5DF2\u4FEE\u6539" });
1758
+ let packageScript = manifest === null ? "unknown" : "missing";
1759
+ let currentScript = null;
1760
+ try {
1761
+ const pkg = JSON.parse(readFileSync4(path10.join(root, "package.json"), "utf8"));
1762
+ currentScript = typeof pkg.scripts?.["verify-notes"] === "string" ? pkg.scripts["verify-notes"] : null;
1763
+ } catch {
1764
+ }
1765
+ if (currentScript === null) {
1766
+ packageScript = "missing";
1767
+ issues.push({ severity: "error", code: "package-script-missing", message: "package.json \u7F3A\u5C11 scripts.verify-notes" });
1768
+ } else if (manifest === null) {
1769
+ packageScript = "unknown";
1770
+ } else if (manifest.managed?.packageScript !== null && manifest.managed?.packageScript !== void 0 && currentScript !== manifest.managed.packageScript) {
1771
+ packageScript = "customized";
1772
+ issues.push({ severity: "warning", code: "package-script-customized", message: "scripts.verify-notes \u4E0E init \u65F6\u4E0D\u540C" });
1773
+ } else packageScript = "ok";
1774
+ const workflowPath = manifest?.managed?.workflow ?? ".github/workflows/verify-notes.yml";
1775
+ const workflow = existsSync5(path10.join(root, workflowPath)) ? "existing" : "missing";
1776
+ if (workflow === "missing") issues.push({ severity: "warning", code: "workflow-missing", message: `${workflowPath} \u7F3A\u5931` });
1777
+ let notes = "ok";
1778
+ for (const lifecycle of ["proposed", "implemented", "rejected", "archived"]) {
1779
+ if (!existsSync5(path10.join(root, ".agents", "notes", lifecycle))) {
1780
+ notes = "missing";
1781
+ issues.push({ severity: "error", code: "notes-missing", message: `.agents/notes/${lifecycle} \u7F3A\u5931` });
1782
+ }
1783
+ }
1784
+ const currentVersion = packageVersion();
1785
+ if (manifest !== null && manifest.gateVersion !== currentVersion) {
1786
+ issues.push({ severity: "warning", code: "stale-init", message: `init gateVersion=${manifest.gateVersion}\uFF0C\u5F53\u524D=${currentVersion}\uFF1B\u5EFA\u8BAE\u91CD\u65B0\u8FD0\u884C /gate-init` });
1787
+ }
1788
+ const ok = !issues.some((issue) => issue.severity === "error");
1789
+ const lines = [
1790
+ `init verify\uFF1A${ok ? "ok" : "FAILED"}`,
1791
+ `manifest\uFF1A${manifest === null ? "missing/invalid" : "ok"}`,
1792
+ `gateVersion\uFF1A${manifest?.gateVersion ?? "unknown"}\uFF08current ${currentVersion}\uFF09`,
1793
+ `artifact-policy\uFF1A${artifactPolicy}`,
1794
+ `agents block\uFF1A${agentsBlock}`,
1795
+ `package script\uFF1A${packageScript}`,
1796
+ `workflow\uFF1A${workflow}`,
1797
+ `notes\uFF1A${notes}`,
1798
+ ...issues.map((issue) => `${issue.severity}: ${issue.message}`)
1799
+ ];
1800
+ return { ok, manifest: manifest === null ? manifestExists ? "invalid" : "missing" : "ok", immutable, artifactPolicy, agentsBlock, packageScript, workflow, notes, issues, text: lines.join("\n") };
1801
+ }
1802
+
1624
1803
  // src/commands.ts
1625
1804
  import { execFileSync as execFileSync3 } from "node:child_process";
1626
- import { readFileSync as readFileSync4 } from "node:fs";
1805
+ import { readFileSync as readFileSync5 } from "node:fs";
1627
1806
 
1628
1807
  // src/command-catalog.ts
1629
1808
  var GATE_COMMANDS = [
@@ -1691,25 +1870,28 @@ function registerCommands(ctx, deps) {
1691
1870
  command2("gate-init", async (invocation) => {
1692
1871
  const workspace = workspaceBoundary(ctx, invocation.agent);
1693
1872
  if (workspace === void 0) return { kind: "error", text: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u4F1A\u8BDD\u5DE5\u4F5C\u533A" };
1694
- const root = path10.resolve(workspace);
1873
+ const root = path11.resolve(workspace);
1695
1874
  let provisioned;
1696
1875
  let projectScripts;
1697
1876
  let projectCi;
1698
1877
  let projectGit;
1699
1878
  let artifactPolicy;
1879
+ let initManifest;
1700
1880
  try {
1701
1881
  provisioned = provisionNotes(root);
1702
1882
  artifactPolicy = provisionArtifactPolicy(root);
1703
1883
  projectCi = provisionCi(root);
1704
1884
  projectScripts = provisionProjectScripts(root);
1705
1885
  projectGit = provisionGit(root);
1886
+ initManifest = provisionInitManifest(root, projectScripts.command);
1706
1887
  } catch (error) {
1707
1888
  return { kind: "error", text: `notes \u521D\u59CB\u5316\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}` };
1708
1889
  }
1709
- wake(invocation, store.read(root, invocation.agent), `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09\uFF0Cnotes \u5DF2\u521D\u59CB\u5316\u4E3A ${provisioned.notes}\uFF0Cartifact policy \u4E3A ${artifactPolicy.path}\uFF08${artifactPolicy.status}\uFF09\uFF0CAGENTS.md \u5DF2\u66F4\u65B0\uFF0Cverify-notes \u5DF2\u914D\u7F6E\u4E3A ${projectScripts.command}\uFF0CCI \u5DF2\u914D\u7F6E\u4E3A ${projectCi.workflow}\uFF0Cgit\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}\uFF0Cignore\uFF1A${projectGit.ignore}\u3002`, "gate-init");
1890
+ wake(invocation, store.read(root, invocation.agent), `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09\uFF0Cnotes \u5DF2\u521D\u59CB\u5316\u4E3A ${provisioned.notes}\uFF0Cartifact policy \u4E3A ${artifactPolicy.path}\uFF08${artifactPolicy.status}\uFF09\uFF0Cinit manifest \u4E3A ${initManifest.path}\uFF08${initManifest.status}\uFF09\uFF0CAGENTS.md \u5DF2\u66F4\u65B0\uFF0Cverify-notes \u5DF2\u914D\u7F6E\u4E3A ${projectScripts.command}\uFF0CCI \u5DF2\u914D\u7F6E\u4E3A ${projectCi.workflow}\uFF0Cgit\uFF1A${projectGit.git}${projectGit.gitError === null ? "" : `\uFF08${projectGit.gitError}\uFF09`}\uFF0Cignore\uFF1A${projectGit.ignore}\u3002`, "gate-init");
1710
1891
  return { kind: "success", text: `Gate \u5DF2\u521D\u59CB\u5316\uFF1A${root}\uFF08phase=discuss\uFF09
1711
1892
  notes\uFF1A${provisioned.notes}
1712
1893
  artifact policy\uFF1A${artifactPolicy.path}\uFF08${artifactPolicy.status}\uFF09
1894
+ init manifest\uFF1A${initManifest.path}\uFF08${initManifest.status}\uFF09
1713
1895
  AGENTS\uFF1A${provisioned.agents}
1714
1896
  verify-notes\uFF1A${projectScripts.command}
1715
1897
  CI\uFF1A${projectCi.workflow}
@@ -1753,7 +1935,7 @@ ignore\uFF1A${projectGit.ignore}` };
1753
1935
  const topic = String(invocation.rawInput ?? "").trim() || "\u5F53\u524D\u8BA8\u8BBA";
1754
1936
  let skill = "";
1755
1937
  try {
1756
- skill = readFileSync4(path10.join(SKILLS, "gate-prd", "SKILL.md"), "utf8");
1938
+ skill = readFileSync5(path11.join(SKILLS, "gate-prd", "SKILL.md"), "utf8");
1757
1939
  } catch {
1758
1940
  }
1759
1941
  wake(invocation, state, ["\u8BF7\u6309 gate-prd \u6280\u80FD\u521B\u5EFA\u6216\u66F4\u65B0 PRD\u3002", "\u4E3B\u9898/\u4E0A\u4E0B\u6587\uFF1A" + topic, skill].filter(Boolean).join("\n\n"), "gate-prd");
@@ -1920,8 +2102,8 @@ ${JSON.stringify(check.results, null, 2)}` };
1920
2102
  const node = resolveNodeBinary();
1921
2103
  if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
1922
2104
  try {
1923
- const script = path10.join(NOTES_SKILL_DIR, "scripts", "archive-agent-note.ts");
1924
- const output = execFileSync3(node, [script, "--root", path10.join(gate.root, ".agents", "notes"), ...args], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
2105
+ const script = path11.join(NOTES_SKILL_DIR, "scripts", "archive-agent-note.ts");
2106
+ const output = execFileSync3(node, [script, "--root", path11.join(gate.root, ".agents", "notes"), ...args], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1925
2107
  return { kind: "success", text: String(output).trim() || "Note \u5DF2\u5F52\u6863" };
1926
2108
  } catch (error) {
1927
2109
  return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
@@ -1932,11 +2114,11 @@ ${JSON.stringify(check.results, null, 2)}` };
1932
2114
  const raw = String(invocation.rawInput ?? "").trim();
1933
2115
  const args = raw === "" ? [] : raw.split(/\s+/u);
1934
2116
  const target = args[0] ?? "board.html";
1935
- const title = args[1] ?? `${path10.basename(gate.root)} \u51B3\u7B56\u770B\u677F`;
2117
+ const title = args[1] ?? `${path11.basename(gate.root)} \u51B3\u7B56\u770B\u677F`;
1936
2118
  const node = resolveNodeBinary();
1937
2119
  if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
1938
2120
  try {
1939
- const script = path10.join(NOTES_SKILL_DIR, "scripts", "build-board.ts");
2121
+ const script = path11.join(NOTES_SKILL_DIR, "scripts", "build-board.ts");
1940
2122
  const output = execFileSync3(node, [script, "--init", target, title], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1941
2123
  return { kind: "success", text: String(output).trim() || `\u51B3\u7B56\u770B\u677F\u5DF2\u751F\u6210\uFF1A${target}` };
1942
2124
  } catch (error) {
@@ -1948,8 +2130,8 @@ ${JSON.stringify(check.results, null, 2)}` };
1948
2130
  const node = resolveNodeBinary();
1949
2131
  if (node === void 0) return { kind: "error", text: "notes-runtime-unavailable" };
1950
2132
  try {
1951
- const script = path10.join(NOTES_SKILL_DIR, "scripts", "verify-archived-agent-notes.ts");
1952
- const output = execFileSync3(node, [script, "--root", path10.join(gate.root, ".agents", "notes"), "--write"], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
2133
+ const script = path11.join(NOTES_SKILL_DIR, "scripts", "verify-archived-agent-notes.ts");
2134
+ const output = execFileSync3(node, [script, "--root", path11.join(gate.root, ".agents", "notes"), "--write"], { cwd: gate.root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
1953
2135
  return { kind: "success", text: String(output).trim() || "archived manifest \u5DF2\u5199\u5165" };
1954
2136
  } catch (error) {
1955
2137
  return { kind: "error", text: String(error?.stderr ?? error?.message ?? error).trim() };
@@ -2017,8 +2199,8 @@ function registerStartPhase(ctx, deps) {
2017
2199
  }
2018
2200
 
2019
2201
  // src/tools/record.ts
2020
- import { createHash } from "node:crypto";
2021
- import { existsSync as existsSync5, lstatSync as lstatSync5, readFileSync as readFileSync5 } from "node:fs";
2202
+ import { createHash as createHash2 } from "node:crypto";
2203
+ import { existsSync as existsSync6, lstatSync as lstatSync5, readFileSync as readFileSync6 } from "node:fs";
2022
2204
  function registerRecord(ctx, deps) {
2023
2205
  const { store, reviewLimit, wake } = deps;
2024
2206
  ctx.tools.register({
@@ -2224,12 +2406,12 @@ function registerRecord(ctx, deps) {
2224
2406
  for (const item of evidence) {
2225
2407
  const target = classifyPath(gate.workspace, item);
2226
2408
  try {
2227
- if (target.outside || !existsSync5(target.absolute) || !lstatSync5(target.absolute).isFile()) {
2409
+ if (target.outside || !existsSync6(target.absolute) || !lstatSync5(target.absolute).isFile()) {
2228
2410
  invalidEvidence.push(item);
2229
2411
  continue;
2230
2412
  }
2231
- const bytes = readFileSync5(target.absolute);
2232
- records.push({ path: target.relative, size: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") });
2413
+ const bytes = readFileSync6(target.absolute);
2414
+ records.push({ path: target.relative, size: bytes.length, sha256: createHash2("sha256").update(bytes).digest("hex") });
2233
2415
  } catch {
2234
2416
  invalidEvidence.push(item);
2235
2417
  }
@@ -2347,8 +2529,8 @@ function registerStatus(ctx, deps) {
2347
2529
  }
2348
2530
 
2349
2531
  // src/tools/notes.ts
2350
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
2351
- import path11 from "node:path";
2532
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "node:fs";
2533
+ import path12 from "node:path";
2352
2534
  function registerNoteTools(ctx, deps) {
2353
2535
  const { store, reviewLimit, wake } = deps;
2354
2536
  ctx.tools.register({
@@ -2374,7 +2556,7 @@ function registerNoteTools(ctx, deps) {
2374
2556
  return { ok: false, code: "planning-only", reason: "gate_note_write \u53EA\u5141\u8BB8 planning worker \u5728 plan/plan-revise \u8282\u70B9\u4F7F\u7528\u3002" };
2375
2557
  }
2376
2558
  let relative = String(args.path ?? "").replaceAll("\\", "/").replace(/^\.?\//u, "");
2377
- if (relative === "" || relative.includes("..") || path11.isAbsolute(relative) || !relative.endsWith(".md")) {
2559
+ if (relative === "" || relative.includes("..") || path12.isAbsolute(relative) || !relative.endsWith(".md")) {
2378
2560
  return { ok: false, code: "invalid-path" };
2379
2561
  }
2380
2562
  let lifecycle = "proposed";
@@ -2386,16 +2568,16 @@ function registerNoteTools(ctx, deps) {
2386
2568
  if (!/^(?:feature|bug-fix|simplification|architecture|process|testing)\/\d{4}-\d{2}-\d{2}-.+\.md$/u.test(relative)) {
2387
2569
  return { ok: false, code: "invalid-path", reason: "\u9700\u8981 <class>/yyyy-mm-dd-topic.md\uFF0C\u6216 proposed|implemented/<class>/...\u3002" };
2388
2570
  }
2389
- const base = path11.join(gate.root, ".agents", "notes", String(lifecycle));
2390
- const absolute = path11.resolve(base, relative);
2571
+ const base = path12.join(gate.root, ".agents", "notes", String(lifecycle));
2572
+ const absolute = path12.resolve(base, relative);
2391
2573
  if (!isInside(base, absolute)) return { ok: false, code: "invalid-path" };
2392
2574
  if (pathHasSymlink(base, absolute)) return { ok: false, code: "invalid-path" };
2393
- if (lifecycle === "implemented" && !existsSync6(absolute)) {
2575
+ if (lifecycle === "implemented" && !existsSync7(absolute)) {
2394
2576
  return { ok: false, code: "implemented-must-exist", reason: "implemented Note \u53EA\u80FD\u539F\u5730\u7F16\u8F91\uFF0C\u4E0D\u80FD\u65B0\u5EFA\u3002" };
2395
2577
  }
2396
- mkdirSync5(path11.dirname(absolute), { recursive: true });
2397
- writeFileSync5(absolute, String(args.content ?? ""));
2398
- return { ok: true, path: path11.relative(gate.root, absolute).replaceAll("\\", "/"), lifecycle };
2578
+ mkdirSync6(path12.dirname(absolute), { recursive: true });
2579
+ writeFileSync6(absolute, String(args.content ?? ""));
2580
+ return { ok: true, path: path12.relative(gate.root, absolute).replaceAll("\\", "/"), lifecycle };
2399
2581
  }
2400
2582
  });
2401
2583
  ctx.tools.register({
@@ -2418,7 +2600,7 @@ function registerNoteTools(ctx, deps) {
2418
2600
  if (!check.ok) return { ...check, state: normalizeState(state) };
2419
2601
  if (state.node === "note-lifecycle") {
2420
2602
  if (state.noteFinalized !== true) return { ok: false, code: "note-lifecycle-incomplete", reason: "\u5148\u8C03\u7528 gate_note_finalize\u3002", state: normalizeState(state) };
2421
- const implemented = state.notePaths.length > 0 && state.notePaths.every((entry) => entry.lifecycle === "implemented" && existsSync6(path11.join(gate.root, entry.path)));
2603
+ const implemented = state.notePaths.length > 0 && state.notePaths.every((entry) => entry.lifecycle === "implemented" && existsSync7(path12.join(gate.root, entry.path)));
2422
2604
  if (!implemented) return { ok: false, code: "note-lifecycle-incomplete", reason: "notePaths \u5C1A\u672A\u5168\u90E8\u8FDB\u5165 implemented\u3002", results: check.results, state: normalizeState(state) };
2423
2605
  state.noteVerified = true;
2424
2606
  store.write(gate.root, exec.agent, state);
@@ -2466,9 +2648,9 @@ function registerNoteTools(ctx, deps) {
2466
2648
  const recovered = recoverFinalize(gate.root, sessionId);
2467
2649
  for (const entry of state.notePaths) {
2468
2650
  if (entry.lifecycle !== "proposed") continue;
2469
- const source = path11.join(gate.root, entry.path);
2651
+ const source = path12.join(gate.root, entry.path);
2470
2652
  const targetRelative = entry.path.replaceAll("\\", "/").replace(".agents/notes/proposed/", ".agents/notes/implemented/");
2471
- if (!existsSync6(source) && existsSync6(path11.join(gate.root, targetRelative))) {
2653
+ if (!existsSync7(source) && existsSync7(path12.join(gate.root, targetRelative))) {
2472
2654
  entry.path = targetRelative;
2473
2655
  entry.lifecycle = "implemented";
2474
2656
  recovered.push("adopted-target");
@@ -2511,7 +2693,8 @@ var GATE_TOOLS = [
2511
2693
  { name: "gate_note_finalize", summary: "\u628A proposed Note \u79FB\u5230 implemented \u5E76\u66F4\u65B0\u5934\u5757\uFF1Broot-only\u3002" },
2512
2694
  { name: "gate_help", summary: "\u5217\u51FA gate \u547D\u4EE4\u3001\u5DE5\u5177\u548C\u5F53\u524D phase/node\uFF1B\u53EA\u8BFB\uFF0Croot-only\u3002" },
2513
2695
  { name: "gate_artifact_write", summary: "\u6309 artifact policy \u5199\u767D\u540D\u5355\u6587\u4EF6\uFF1B\u4E0D\u8D70\u666E\u901A workspace write\u3002" },
2514
- { name: "gate_research_dispatch", summary: "root-only \u5728 discuss/plan \u6D3E research worker\uFF1Bworker \u53EA\u5199 research artifact\u3002" }
2696
+ { name: "gate_research_dispatch", summary: "root-only \u5728 discuss/plan \u6D3E research worker\uFF1Bworker \u53EA\u5199 research artifact\u3002" },
2697
+ { name: "gate_init_verify", summary: "root-only \u53EA\u8BFB\u6821\u9A8C init-manifest\u3001\u751F\u6210\u7269 hash\u3001policy \u4E0E init \u7ED3\u6784\u3002" }
2515
2698
  ];
2516
2699
 
2517
2700
  // src/tools/help.ts
@@ -2679,6 +2862,27 @@ function registerResearchDispatch(ctx, deps) {
2679
2862
  });
2680
2863
  }
2681
2864
 
2865
+ // src/tools/init-verify.ts
2866
+ function registerInitVerify(ctx, _deps) {
2867
+ ctx.tools.register({
2868
+ name: "gate_init_verify",
2869
+ description: "root-only \u53EA\u8BFB\u6821\u9A8C /gate-init \u4EA7\u7269\uFF1Ainit-manifest\u3001immutable hash\u3001artifact policy\u3001AGENTS managed block\u3001verify-notes script\u3001workflow\u3001notes \u76EE\u5F55\u3002",
2870
+ parameters: { type: "object", additionalProperties: false, properties: {} },
2871
+ output: {
2872
+ schema: { type: "object", additionalProperties: true },
2873
+ render: (_args, value) => [{ type: "text", text: typeof value?.text === "string" ? value.text : JSON.stringify(value) }]
2874
+ },
2875
+ async execute(_args, exec) {
2876
+ if (rootAgentOf(ctx, exec.agent)?.id !== exec.agent?.id) {
2877
+ return { ok: false, code: "root-only", reason: "gate_init_verify \u53EA\u80FD\u7531 root agent \u8C03\u7528\u3002" };
2878
+ }
2879
+ const gate = resolveGate(ctx, exec.agent);
2880
+ if (gate.root === void 0) return { ok: false, code: "state-unavailable", reason: "\u65E0\u6CD5\u786E\u5B9A\u5DE5\u4F5C\u533A\u3002" };
2881
+ return verifyInit(gate.root);
2882
+ }
2883
+ });
2884
+ }
2885
+
2682
2886
  // src/tools/index.ts
2683
2887
  function registerTools(ctx, deps) {
2684
2888
  registerStartPhase(ctx, deps);
@@ -2688,6 +2892,7 @@ function registerTools(ctx, deps) {
2688
2892
  registerHelp(ctx, deps);
2689
2893
  registerArtifactTools(ctx, deps);
2690
2894
  registerResearchDispatch(ctx, deps);
2895
+ registerInitVerify(ctx, deps);
2691
2896
  }
2692
2897
 
2693
2898
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-gate-agent",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "Phase/node workflow gate with built-in commands, tools, and skills.",
6
6
  "exports": {
@@ -158,7 +158,9 @@ Status: <状态>
158
158
  "verify-notes": "node .agents/gate/verify-notes.mjs"
159
159
  ```
160
160
 
161
- 这个启动器只调用插件内置脚本,不复制技能。
161
+ 这个启动器只调用插件内置脚本,不复制技能。
162
+
163
+ `/gate-init` 还会生成 `.agents/gate/init-manifest.json`,记录 CI 脚本/启动器的 hash 和 init 结构。需要核对初始化是否被改坏时,用 root-only 只读工具 `gate_init_verify`;它不检查 Note 内容,也不要在每次 Note 操作后运行。`init-manifest.json` 由 git 跟踪,同内容重跑 init 时字节稳定。
162
164
 
163
165
  - 计划完成前或提交前,运行 `npm run verify-notes`,检查 tree、format、lifecycle、archived。
164
166
  - `/gate-init` 同时生成 `.github/workflows/verify-notes.yml` 和 `.agents/gate/ci/` 校验脚本副本;CI 使用项目内副本,不依赖插件安装路径。
@@ -33,7 +33,9 @@ Notes 读取/写入命令均接受 `--root <path>` 或 `--root=<path>`,每次
33
33
  ## 接入建议
34
34
 
35
35
  - 轻量(个人/无 git):前两个脚本即可,归档封印在第一次归档后自然生效。
36
- - 完整:运行 `npm run verify-notes` 跑 tree/format/lifecycle/archived 四检;该脚本由 `/gate-init` 生成,只调用插件内置脚本。gate 会话内也可用 `/gate-note-verify`。
36
+ - 完整:运行 `npm run verify-notes` 跑 tree/format/lifecycle/archived 四检;该脚本由 `/gate-init` 生成,只调用插件内置脚本。gate 会话内也可用 `/gate-note-verify`。
37
+
38
+ 初始化结构核对用 root-only 的 `gate_init_verify`;它检查 `init-manifest.json`、生成物 hash、artifact-policy schema、AGENTS block、package script、workflow 和 notes 四目录,不检查 Note 内容,也不应随每次 Note 操作运行。
37
39
 
38
40
 
39
41
  ## 脚本路径