dsh-notes-board 0.1.1 → 0.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/NOTICE.md +7 -0
- package/README.md +115 -13
- package/index.js +21 -178
- package/locale/en.json +1 -1
- package/locale/zh.json +1 -1
- package/package.json +31 -8
- package/scripts/test-board.mjs +15 -1
- package/src/core/notes.js +486 -0
- package/src/core/tools.js +221 -0
- package/src/host/board.js +184 -0
- package/src/host/notes.js +250 -0
- package/src/host/skills.js +48 -0
- package/vendor/write-notes-like-deepseek/README.md +323 -0
- package/vendor/write-notes-like-deepseek/package.json +21 -0
- package/vendor/write-notes-like-deepseek/scripts/export-assets.sh +40 -0
- package/vendor/write-notes-like-deepseek/scripts/import-dsh-notes.ts +125 -0
- package/vendor/write-notes-like-deepseek/scripts/test-skill-install.mjs +63 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/SKILL.md +195 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/assets/agent-notes-board.html +1779 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/archiving.md +109 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/classification.md +33 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/note-format.md +69 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/prose-checklist.md +51 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/quality-gate.md +58 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/simplification-checklist.md +26 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/verification.md +34 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/references/when-to-write.md +83 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/agent-note-tree.ts +75 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/archive-agent-note.ts +167 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/build-board.ts +235 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/check-note-anchors.ts +103 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-format.ts +158 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-agent-note-tree.ts +56 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/scripts/verify-archived-agent-notes.ts +127 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/implemented.md +32 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/proposed.md +38 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/rejected.md +27 -0
- package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/verify-notes.yml +38 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify tree: lifecycle/class/filename/INDEX and internal relative markdown links.
|
|
3
|
+
* Run: npx tsx <skill-dir>/scripts/verify-agent-note-tree.ts
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { dirname, resolve, sep } from "node:path";
|
|
7
|
+
import { agentNoteRoot, walkAgentNoteTree } from "./agent-note-tree.ts";
|
|
8
|
+
|
|
9
|
+
const { notes, errors } = walkAgentNoteTree();
|
|
10
|
+
|
|
11
|
+
// Check relative markdown links inside active notes
|
|
12
|
+
const LINK_REGEX = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
13
|
+
|
|
14
|
+
for (const note of notes) {
|
|
15
|
+
const noteFullPath = resolve(agentNoteRoot, note.rel);
|
|
16
|
+
const content = readFileSync(noteFullPath, "utf8");
|
|
17
|
+
let match: RegExpExecArray | null;
|
|
18
|
+
|
|
19
|
+
while ((match = LINK_REGEX.exec(content)) !== null) {
|
|
20
|
+
let rawTarget = match[2]?.trim();
|
|
21
|
+
if (!rawTarget) continue;
|
|
22
|
+
|
|
23
|
+
// Support CommonMark angle bracket destinations: [text](<path>)
|
|
24
|
+
if (rawTarget.startsWith("<") && rawTarget.endsWith(">")) {
|
|
25
|
+
rawTarget = rawTarget.slice(1, -1).trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Ignore web links, intra-page anchors and ellipsis placeholders
|
|
29
|
+
if (rawTarget.startsWith("http://") || rawTarget.startsWith("https://") || rawTarget.startsWith("#") || rawTarget.startsWith("mailto:") || rawTarget.includes("…")) {
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Strip anchor fragment if present
|
|
34
|
+
const fileTarget = rawTarget.split("#")[0];
|
|
35
|
+
if (!fileTarget) continue;
|
|
36
|
+
|
|
37
|
+
const resolvedTarget = resolve(dirname(noteFullPath), fileTarget);
|
|
38
|
+
|
|
39
|
+
// Only verify internal links within agentNoteRoot (boundary-aware: a
|
|
40
|
+
// sibling like .agents/notes-backup/ must not prefix-match)
|
|
41
|
+
if (!resolvedTarget.startsWith(agentNoteRoot + sep)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!existsSync(resolvedTarget)) {
|
|
46
|
+
errors.push(`link: ${note.rel} -> "${rawTarget}" target file does not exist`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (errors.length) {
|
|
52
|
+
for (const e of errors) console.error(e);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(`ok: ${notes.length} note(s) tree and relative links verified`);
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify frozen archived Agent Notes: exact head layout, manifest.json seals,
|
|
3
|
+
* append-only extension vs a git baseline (skipped gracefully without git).
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx tsx <skill-dir>/scripts/verify-archived-agent-notes.ts # verify only
|
|
6
|
+
* npx tsx <skill-dir>/scripts/verify-archived-agent-notes.ts --write # verify, then seal unsealed files
|
|
7
|
+
* Env: AGENT_NOTE_ARCHIVE_BASE_REF (default HEAD) — git ref the manifest is compared against.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { execFileSync } from "node:child_process";
|
|
11
|
+
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { join, relative } from "node:path";
|
|
13
|
+
import { agentNoteRoot } from "./agent-note-tree.ts";
|
|
14
|
+
|
|
15
|
+
const isWrite = process.argv.includes("--write");
|
|
16
|
+
const errors: string[] = [];
|
|
17
|
+
const warnings: string[] = [];
|
|
18
|
+
const fail = (msg: string) => { errors.push(msg); };
|
|
19
|
+
|
|
20
|
+
// --- collect archived files (skip accidental .zh.md)
|
|
21
|
+
const archivedDir = join(agentNoteRoot, "archived");
|
|
22
|
+
const files: string[] = [];
|
|
23
|
+
function scan(dir: string) {
|
|
24
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
25
|
+
if (entry.name.startsWith(".")) continue;
|
|
26
|
+
const full = join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) scan(full);
|
|
28
|
+
else if (entry.isFile() && entry.name.endsWith(".md") && !entry.name.endsWith(".zh.md")) {
|
|
29
|
+
const rel = relative(archivedDir, full).split("\\").join("/");
|
|
30
|
+
if (rel === "AGENTS.md" || rel === "manifest.json") continue;
|
|
31
|
+
files.push(rel);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (existsSync(archivedDir)) scan(archivedDir);
|
|
36
|
+
|
|
37
|
+
// --- head layout: L1 title / L2 blank / L3 Status / L4 Archived / L5 blank
|
|
38
|
+
const TITLE_RE = /^# Agent Note[::] ?\S/;
|
|
39
|
+
const ARCHIVED_RE = /^Archived: \d{4}-\d{2}-\d{2}$/;
|
|
40
|
+
for (const rel of files) {
|
|
41
|
+
const lines = readFileSync(join(archivedDir, rel), "utf8").replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").split("\n");
|
|
42
|
+
if (!TITLE_RE.test(lines[0] ?? "")) fail(`${rel} — line 1 must be \`# Agent Note: <title>\``);
|
|
43
|
+
if (lines[1] !== "") fail(`${rel} — line 2 must be blank`);
|
|
44
|
+
if (lines[2] !== "Status: implemented") fail(`${rel} — line 3 must be \`Status: implemented\``);
|
|
45
|
+
if (!ARCHIVED_RE.test(lines[3] ?? "")) fail(`${rel} — line 4 must be \`Archived: YYYY-MM-DD\` immediately below Status`);
|
|
46
|
+
if (lines[4] !== "") fail(`${rel} — line 5 must be blank after Archived`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- manifest seals
|
|
50
|
+
const manifestPath = join(archivedDir, "manifest.json");
|
|
51
|
+
interface Manifest { version: 1; files: Record<string, string>; }
|
|
52
|
+
let manifest: Manifest = { version: 1, files: {} };
|
|
53
|
+
if (existsSync(manifestPath)) {
|
|
54
|
+
try {
|
|
55
|
+
manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
56
|
+
} catch {
|
|
57
|
+
fail("archived/manifest.json exists but is not valid JSON");
|
|
58
|
+
}
|
|
59
|
+
} else if (files.length > 0 && !isWrite) {
|
|
60
|
+
fail("archived/manifest.json missing — run with --write to seal existing archived notes");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sealOf = (relFromRoot: string) =>
|
|
64
|
+
`sha256:${createHash("sha256").update(readFileSync(join(agentNoteRoot, relFromRoot))).digest("hex")}`;
|
|
65
|
+
|
|
66
|
+
for (const rel of files) {
|
|
67
|
+
const key = `archived/${rel}`;
|
|
68
|
+
const entry = manifest.files[key];
|
|
69
|
+
if (!entry) {
|
|
70
|
+
if (isWrite) {
|
|
71
|
+
manifest.files[key] = sealOf(key);
|
|
72
|
+
} else {
|
|
73
|
+
fail(`${key} — missing seal in manifest.json (run with --write)`);
|
|
74
|
+
}
|
|
75
|
+
} else if (entry !== sealOf(key)) {
|
|
76
|
+
fail(`${key} — seal mismatch: archived note was modified after sealing (frozen notes must never change)`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const key of Object.keys(manifest.files)) {
|
|
80
|
+
if (!existsSync(join(agentNoteRoot, key))) fail(`${key} — sealed entry has no file on disk`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- append-only vs git baseline (skipped without git)
|
|
84
|
+
const repoRoot = (() => {
|
|
85
|
+
try {
|
|
86
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], { cwd: agentNoteRoot, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
})();
|
|
91
|
+
if (repoRoot) {
|
|
92
|
+
const baseRef = process.env.AGENT_NOTE_ARCHIVE_BASE_REF || "HEAD";
|
|
93
|
+
const manifestRel = relative(repoRoot, manifestPath).split("\\").join("/");
|
|
94
|
+
let baselineRaw: string | null = null;
|
|
95
|
+
try {
|
|
96
|
+
baselineRaw = execFileSync("git", ["show", `${baseRef}:${manifestRel}`], { cwd: repoRoot, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
|
|
97
|
+
} catch {
|
|
98
|
+
// manifest absent at baseline — nothing to compare
|
|
99
|
+
}
|
|
100
|
+
if (baselineRaw !== null) {
|
|
101
|
+
try {
|
|
102
|
+
const baseline = JSON.parse(baselineRaw) as Manifest;
|
|
103
|
+
for (const [key, seal] of Object.entries(baseline.files ?? {})) {
|
|
104
|
+
if (manifest.files[key] !== seal) {
|
|
105
|
+
fail(`${key} — seal added/changed/removed relative to ${baseRef}; archived seals are append-only`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
fail(`archived/manifest.json at ${baseRef} was not valid JSON`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
warnings.push("not a git repository — append-only check skipped (seal hashes still verified against disk)");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (errors.length) {
|
|
117
|
+
for (const e of errors) console.error(`archived: ${e}`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (isWrite) {
|
|
122
|
+
const sorted = Object.fromEntries(Object.entries(manifest.files).sort(([a], [b]) => a.localeCompare(b)));
|
|
123
|
+
writeFileSync(manifestPath, JSON.stringify({ version: 1, files: sorted }, null, 2) + "\n", "utf8");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const w of warnings) console.warn(`warning: ${w}`);
|
|
127
|
+
console.log(`ok: ${files.length} archived note(s) verified, ${Object.keys(manifest.files).length} seal(s) in manifest`);
|
package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/implemented.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Agent Note: <标题>
|
|
2
|
+
|
|
3
|
+
Status: implemented
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
【维护提醒】
|
|
7
|
+
1. 现行法律契约:代码重构、改名或路径变更时,同批就地更新事实。禁止把 Decision 改写成另一个决定。
|
|
8
|
+
2. 时态纪律:严禁 ## Proposal / ## Plan / ## Acceptance criteria 等计划态标题;Decision 用现在时(门禁只拒标题)。
|
|
9
|
+
3. 归档契约:只插 Archived: YYYY-MM-DD 紧邻 Status: implemented。互链写在新笔记,不写进归档篇。
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
## Problem
|
|
13
|
+
|
|
14
|
+
<动机:脱离方案也能独立成立。说明触发原因:什么破了、什么要变、不做会怎样>
|
|
15
|
+
|
|
16
|
+
## Decision
|
|
17
|
+
|
|
18
|
+
<已落地事实,全篇使用现在时描述。可插入 Package 拓扑 / 契约 / Schema 等业务自由小节>
|
|
19
|
+
|
|
20
|
+
## Alternatives considered
|
|
21
|
+
|
|
22
|
+
- **<备选 A>** — <先写其最强论据,再写为何被否>
|
|
23
|
+
- **<备选 B>** — <先写其最强论据,再写为何被否。不做/复用仅当当时真的权衡过才写,不要编>
|
|
24
|
+
|
|
25
|
+
## Consequences
|
|
26
|
+
|
|
27
|
+
- **收益**:<带来了什么确定性提升,无基线不写相对量词>
|
|
28
|
+
- **代价与已知上限**:<牺牲了什么,什么信号发生时必须重访该决定>
|
|
29
|
+
|
|
30
|
+
## Verification
|
|
31
|
+
|
|
32
|
+
<可选,现在时事实:如何证明该决定在当前代码库中依然生效。落到可确认表面——路径、量级、执行命令>
|
package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/proposed.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Agent Note: <标题>
|
|
2
|
+
|
|
3
|
+
Status: proposed
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
【维护提醒】
|
|
7
|
+
1. 落地转 implemented 时的重写契约:## Proposal 改为现在时的 ## Decision;
|
|
8
|
+
## Acceptance criteria / ## Risks 折进 ## Consequences(或现在时的 ## Testing / ## Verification)。
|
|
9
|
+
2. 前提消失或不再推进 → 转 Status: rejected — <原因>,严禁归档 proposed。
|
|
10
|
+
3. Alternatives 必填:只记录真实考虑过的对手方案;「不做/复用」仅当当时真的权衡过才写。
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
<动机,脱离方案也能独立成立;写清背景、约束与失败条件>
|
|
16
|
+
|
|
17
|
+
## Proposal
|
|
18
|
+
|
|
19
|
+
<拟议改动,可用将来时;含关键设计、迁移步骤与开放问题>
|
|
20
|
+
|
|
21
|
+
## Alternatives considered
|
|
22
|
+
|
|
23
|
+
必填。只写当时真的权衡过的选项(没有过的不要编)。「不做 / 复用现状」可以是其中一档,但不是必填档。
|
|
24
|
+
|
|
25
|
+
每档一个候选,或 `### Why not <X>?` 子节:
|
|
26
|
+
|
|
27
|
+
- **<备选 A>** — <为何没选,先写它的最强论据再写否掉理由,一段一备选>
|
|
28
|
+
- **<备选 B>** — <同上>
|
|
29
|
+
|
|
30
|
+
需要结构化对比多个选项时,用标准 × 选项矩阵(Fit / 运维复杂度 / 团队技能 / 成本 / 锁定风险 / 实施时间)逐格打 ✓/⚠️/✗。被否项必带"为何否"。
|
|
31
|
+
|
|
32
|
+
## Acceptance criteria
|
|
33
|
+
|
|
34
|
+
<什么可观察状态算完成;含验证表面与门禁>
|
|
35
|
+
|
|
36
|
+
## Risks
|
|
37
|
+
|
|
38
|
+
<可能出什么问题、已知取舍与回滚条件>
|
package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/rejected.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Agent Note: <标题>
|
|
2
|
+
|
|
3
|
+
Status: rejected — <一句话原因>
|
|
4
|
+
|
|
5
|
+
<!--
|
|
6
|
+
【维护提醒】
|
|
7
|
+
1. 冻结不动:不改写为对立面,不随代码演进更新事实;结论就在 Status 行。
|
|
8
|
+
2. 仅在"防止重犯"期间保留:被否方案依赖的旧技术/API 已彻底移除、后人绝无可能
|
|
9
|
+
再提时,把这一篇删掉。
|
|
10
|
+
3. 价值被新笔记吸收时,按 references/archiving.md 的合并删除流程处理。
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
## Problem
|
|
14
|
+
|
|
15
|
+
<动机>
|
|
16
|
+
|
|
17
|
+
## Proposal
|
|
18
|
+
|
|
19
|
+
<曾拟议的方案,冻结保留>
|
|
20
|
+
|
|
21
|
+
## Alternatives considered
|
|
22
|
+
|
|
23
|
+
- **<备选>** — <为何当时考虑/为何最终否掉>
|
|
24
|
+
|
|
25
|
+
## Risks
|
|
26
|
+
|
|
27
|
+
<为何否掉、若重提需满足什么条件>
|
package/vendor/write-notes-like-deepseek/skills/write-notes-like-deepseek/templates/verify-notes.yml
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# 复制到宿主仓库 .github/workflows/verify-notes.yml 后启用。
|
|
2
|
+
# 默认项目级安装路径;使用其他路径时请同步修改下方命令。
|
|
3
|
+
name: Verify Agent Notes
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
verify:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout
|
|
19
|
+
uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Setup Node
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version: 24
|
|
27
|
+
|
|
28
|
+
- name: Verify note tree, format, and archive seals
|
|
29
|
+
env:
|
|
30
|
+
# HEAD is a no-op on a clean checkout. Compare against the pre-change
|
|
31
|
+
# commit: PR base, or the previous tip of the pushed branch.
|
|
32
|
+
# A new-branch push sends before=0000…; git show fails and the script
|
|
33
|
+
# skips append-only (seal-vs-disk still runs).
|
|
34
|
+
AGENT_NOTE_ARCHIVE_BASE_REF: ${{ github.event.pull_request.base.sha || github.event.before }}
|
|
35
|
+
run: |
|
|
36
|
+
npx tsx .agents/skills/write-notes-like-deepseek/scripts/verify-agent-note-tree.ts
|
|
37
|
+
npx tsx .agents/skills/write-notes-like-deepseek/scripts/verify-agent-note-format.ts
|
|
38
|
+
npx tsx .agents/skills/write-notes-like-deepseek/scripts/verify-archived-agent-notes.ts
|