openspec-playwright 0.3.73 → 0.3.74

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.
Files changed (43) hide show
  1. package/dist/commands/editors/adapters/claude.d.ts +5 -0
  2. package/dist/commands/editors/adapters/claude.js +79 -0
  3. package/dist/commands/editors/adapters/claude.js.map +1 -0
  4. package/dist/commands/editors/adapters/cline.d.ts +15 -0
  5. package/dist/commands/editors/adapters/cline.js +63 -0
  6. package/dist/commands/editors/adapters/cline.js.map +1 -0
  7. package/dist/commands/editors/adapters/cursor.d.ts +15 -0
  8. package/dist/commands/editors/adapters/cursor.js +79 -0
  9. package/dist/commands/editors/adapters/cursor.js.map +1 -0
  10. package/dist/commands/editors/adapters/dsh.d.ts +27 -0
  11. package/dist/commands/editors/adapters/dsh.js +67 -0
  12. package/dist/commands/editors/adapters/dsh.js.map +1 -0
  13. package/dist/commands/editors/adapters/omp.d.ts +24 -0
  14. package/dist/commands/editors/adapters/omp.js +73 -0
  15. package/dist/commands/editors/adapters/omp.js.map +1 -0
  16. package/dist/commands/editors/adapters/opencode.d.ts +8 -0
  17. package/dist/commands/editors/adapters/opencode.js +133 -0
  18. package/dist/commands/editors/adapters/opencode.js.map +1 -0
  19. package/dist/commands/editors/adapters/pi.d.ts +27 -0
  20. package/dist/commands/editors/adapters/pi.js +64 -0
  21. package/dist/commands/editors/adapters/pi.js.map +1 -0
  22. package/dist/commands/editors/project-rules.d.ts +69 -0
  23. package/dist/commands/editors/project-rules.js +246 -0
  24. package/dist/commands/editors/project-rules.js.map +1 -0
  25. package/dist/commands/editors/registry.d.ts +12 -0
  26. package/dist/commands/editors/registry.js +59 -0
  27. package/dist/commands/editors/registry.js.map +1 -0
  28. package/dist/commands/editors/shared.d.ts +37 -0
  29. package/dist/commands/editors/shared.js +92 -0
  30. package/dist/commands/editors/shared.js.map +1 -0
  31. package/dist/commands/editors/tool-selection.d.ts +17 -0
  32. package/dist/commands/editors/tool-selection.js +60 -0
  33. package/dist/commands/editors/tool-selection.js.map +1 -0
  34. package/dist/commands/editors/types.d.ts +82 -0
  35. package/dist/commands/editors/types.js +41 -0
  36. package/dist/commands/editors/types.js.map +1 -0
  37. package/dist/commands/editors.d.ts +32 -309
  38. package/dist/commands/editors.js +38 -960
  39. package/dist/commands/editors.js.map +1 -1
  40. package/dist/commands/update.d.ts +8 -0
  41. package/dist/commands/update.js +1 -1
  42. package/dist/commands/update.js.map +1 -1
  43. package/package.json +5 -5
@@ -0,0 +1,27 @@
1
+ import { type EditorAdapter, type CommandMeta } from "../types.js";
2
+ /**
3
+ * Pi stores project resources in `.pi/` (skills/, prompts/, extensions/).
4
+ * Prompt templates are Markdown with optional YAML frontmatter; the
5
+ * filename becomes the slash command name (`opsx-e2e.md` → `/opsx-e2e`),
6
+ * `description` feeds autocomplete, and `argument-hint` shows expected
7
+ * arguments. `$1` / `$ARGUMENTS` placeholders expand at prompt time.
8
+ *
9
+ * Conventions follow the Pi docs (packages/coding-agent/docs):
10
+ * - Prompts: `.pi/prompts/*.md`, invoked via `/name`.
11
+ * - Skills: `.pi/skills/` (root `.md` files or `SKILL.md` dirs),
12
+ * invoked via `/skill:name`.
13
+ * - Rules: Pi loads `AGENTS.md` (or `CLAUDE.md`) walking up from cwd
14
+ * natively — no wrapper file needed.
15
+ * - MCP: Pi has NO MCP client (built-in tools only). The adapter
16
+ * still implements the interface with no-ops and declares
17
+ * `supportsMcp: false` so shared MCP phases skip it.
18
+ */
19
+ export declare function formatPiCommand(meta: CommandMeta): string;
20
+ export declare function getPiCommandPath(id: string): string;
21
+ /**
22
+ * True when Pi is in use: a project `.pi/` dir, or a global Pi config dir
23
+ * (`~/.pi/agent/`, created by Pi on first run). The home signal lets
24
+ * `openspec-pw init` detect Pi in a fresh project that has no `.pi/` yet.
25
+ */
26
+ export declare function hasPi(projectRoot: string, homeDir?: string): boolean;
27
+ export declare const piAdapter: EditorAdapter;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Pi adapter: prompt template at `.pi/prompts/opsx-<id>.md` (filename =
3
+ * slash command). NO MCP client — `supportsMcp: false` makes shared MCP
4
+ * phases skip it. Detected via `.pi/` or global `~/.pi/agent/`.
5
+ */
6
+ import { existsSync } from "fs";
7
+ import { homedir } from "os";
8
+ import { join } from "path";
9
+ import { defineAdapter } from "../types.js";
10
+ import { escapeYamlValue, transformToHyphenCommands } from "../shared.js";
11
+ import { registerAdapter } from "../registry.js";
12
+ // ─── Pi adapter ──────────────────────────────────────────────────────────
13
+ /**
14
+ * Pi stores project resources in `.pi/` (skills/, prompts/, extensions/).
15
+ * Prompt templates are Markdown with optional YAML frontmatter; the
16
+ * filename becomes the slash command name (`opsx-e2e.md` → `/opsx-e2e`),
17
+ * `description` feeds autocomplete, and `argument-hint` shows expected
18
+ * arguments. `$1` / `$ARGUMENTS` placeholders expand at prompt time.
19
+ *
20
+ * Conventions follow the Pi docs (packages/coding-agent/docs):
21
+ * - Prompts: `.pi/prompts/*.md`, invoked via `/name`.
22
+ * - Skills: `.pi/skills/` (root `.md` files or `SKILL.md` dirs),
23
+ * invoked via `/skill:name`.
24
+ * - Rules: Pi loads `AGENTS.md` (or `CLAUDE.md`) walking up from cwd
25
+ * natively — no wrapper file needed.
26
+ * - MCP: Pi has NO MCP client (built-in tools only). The adapter
27
+ * still implements the interface with no-ops and declares
28
+ * `supportsMcp: false` so shared MCP phases skip it.
29
+ */
30
+ export function formatPiCommand(meta) {
31
+ const body = transformToHyphenCommands(meta.body);
32
+ return `---
33
+ description: ${escapeYamlValue(meta.description)}
34
+ argument-hint: "<change-name>"
35
+ ---
36
+
37
+ ${body}
38
+ `;
39
+ }
40
+ export function getPiCommandPath(id) {
41
+ return join(".pi", "prompts", `opsx-${id}.md`);
42
+ }
43
+ /**
44
+ * True when Pi is in use: a project `.pi/` dir, or a global Pi config dir
45
+ * (`~/.pi/agent/`, created by Pi on first run). The home signal lets
46
+ * `openspec-pw init` detect Pi in a fresh project that has no `.pi/` yet.
47
+ */
48
+ export function hasPi(projectRoot, homeDir = homedir()) {
49
+ return (existsSync(join(projectRoot, ".pi")) ||
50
+ existsSync(join(homeDir, ".pi", "agent")));
51
+ }
52
+ export const piAdapter = defineAdapter({
53
+ id: "pi",
54
+ label: "pi",
55
+ displayName: "Pi",
56
+ detect: hasPi,
57
+ // Pi has no MCP client — shared MCP phases skip this adapter.
58
+ supportsMcp: false,
59
+ commandFilePath: getPiCommandPath,
60
+ formatCommand: formatPiCommand,
61
+ // Pi loads AGENTS.md natively (walking up from cwd) — that's the default.
62
+ });
63
+ registerAdapter(piAdapter);
64
+ //# sourceMappingURL=pi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.js","sourceRoot":"","sources":["../../../../src/commands/editors/adapters/pi.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,aAAa,EAAwC,MAAM,aAAa,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,4EAA4E;AAE5E;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB;IAC/C,MAAM,IAAI,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO;eACM,eAAe,CAAC,IAAI,CAAC,WAAW,CAAC;;;;EAI9C,IAAI;CACL,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAU;IACzC,OAAO,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,KAAK,CACnB,WAAmB,EACnB,OAAO,GAAG,OAAO,EAAE;IAEnB,OAAO,CACL,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QACpC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAC1C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,SAAS,GAAkB,aAAa,CAAC;IACpD,EAAE,EAAE,IAAI;IACR,KAAK,EAAE,IAAI;IACX,WAAW,EAAE,IAAI;IACjB,MAAM,EAAE,KAAK;IACb,8DAA8D;IAC9D,WAAW,EAAE,KAAK;IAClB,eAAe,EAAE,gBAAgB;IACjC,aAAa,EAAE,eAAe;IAC9B,0EAA0E;CAC3E,CAAC,CAAC;AAEH,eAAe,CAAC,SAAS,CAAC,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { EditorAdapter } from "./types.js";
2
+ /**
3
+ * Read the OPENSPEC marker block from a rules file, or `null` when the file
4
+ * is missing / has no markers. Used by drift detection and update to decide
5
+ * whether a rules file needs rewriting.
6
+ */
7
+ export declare function readOpenSpecBlock(projectRoot: string, adapter: EditorAdapter): string | null;
8
+ /**
9
+ * Whether a rules file's OPENSPEC block matches the expected content.
10
+ * A missing file or absent/truncated markers counts as "does not match"
11
+ * (the caller will rewrite it), which keeps update idempotent but safe.
12
+ */
13
+ export declare function blockMatchesExpected(projectRoot: string, adapter: EditorAdapter, expected: string): boolean;
14
+ /**
15
+ * Install employee-grade standards into the editor's rules file
16
+ * (CLAUDE.md for Claude, AGENTS.md for OpenCode, Cline, and Cursor). Wraps content in
17
+ * `<!-- OPENSPEC:START -->` / `<!-- OPENSPEC:END -->` markers so future
18
+ * updates can replace the block without touching the rest of the file.
19
+ */
20
+ export declare function installOpenSpecBlock(projectRoot: string, standardsContent: string, adapter?: EditorAdapter): void;
21
+ /**
22
+ * The expected OPENSPEC block content for a thin CLAUDE.md wrapper
23
+ * (CodeGraph-first guidance + workflow hint + `@AGENTS.md` import). Exported
24
+ * so drift detection / update can compare against it.
25
+ *
26
+ * The `@AGENTS.md` line is Claude Code's documented way to reuse AGENTS.md
27
+ * inside CLAUDE.md — AGENTS.md is NOT read by default ("Claude Code reads
28
+ * CLAUDE.md, not AGENTS.md"). Contract per
29
+ * https://code.claude.com/docs/en/memory.md:
30
+ * - Position is irrelevant — the doc says "@ ... anywhere in your CLAUDE.md"
31
+ * (examples even inline it mid-sentence or in a list item). The one real
32
+ * constraint: the `@` line must NOT sit inside a code span (backticks) or
33
+ * a fenced code block — the resolver skips those. This wrapper keeps the
34
+ * import at the end of the block as a bare line.
35
+ * - The path resolves relative to the importing CLAUDE.md; import recursion
36
+ * is capped at 4 hops.
37
+ * - Block-level HTML comments (`<!-- ... -->`) are stripped before context
38
+ * injection, so the OPENSPEC markers vanish while the live `@AGENTS.md`
39
+ * line inside them is still honored.
40
+ */
41
+ export declare function claudeWrapperStandardsContent(): string;
42
+ /**
43
+ * Install a thin CLAUDE.md that imports AGENTS.md.
44
+ *
45
+ * Uses the same OPENSPEC:START/END markers as the full standards block so
46
+ * `cleanProjectRules` can remove it uniformly. The CodeGraph-first block is
47
+ * written directly into CLAUDE.md (before the @AGENTS.md import) so Claude
48
+ * Code picks it up without depending on the import.
49
+ *
50
+ * Also handles migration: if CLAUDE.md has an existing OPENSPEC:START block
51
+ * (old format that wrote standards directly to CLAUDE.md), calling
52
+ * `installOpenSpecBlock` replaces the content with the CodeGraph block +
53
+ * `@AGENTS.md` import.
54
+ */
55
+ export declare function installClaudeWrapper(projectRoot: string): void;
56
+ /**
57
+ * Route employee-grade standards into project rules files.
58
+ *
59
+ * AGENTS.md is always the single source of truth, regardless of which
60
+ * editors are detected. If Claude is in use, a thin CLAUDE.md wrapper
61
+ * with `@AGENTS.md` import is created so Claude loads AGENTS.md as
62
+ * its project rules. Cline and Cursor auto-detect AGENTS.md natively — no
63
+ * wrapper needed.
64
+ */
65
+ export declare function installProjectRules(projectRoot: string, standardsContent: string, detected: EditorAdapter[]): void;
66
+ /** Remove all OpenSpec marker blocks from AGENTS.md (always) and CLAUDE.md (for claude adapter). */
67
+ export declare function cleanProjectRules(adapter: EditorAdapter, projectRoot: string): void;
68
+ /** Read the employee-grade standards source file (empty string if missing). */
69
+ export declare function readEmployeeStandards(srcPath: string): string;
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Project rules file management: OPENSPEC marker blocks inside
3
+ * AGENTS.md (single source of truth) and the thin CLAUDE.md wrapper for
4
+ * Claude Code (@AGENTS.md import + CodeGraph-first guidance).
5
+ */
6
+ import { existsSync, lstatSync, rmSync, readFileSync, writeFileSync, } from "fs";
7
+ import { join, basename } from "path";
8
+ import chalk from "chalk";
9
+ import { claudeAdapter } from "./adapters/claude.js";
10
+ import { opencodeAdapter, readOpenCodeInstructions } from "./adapters/opencode.js";
11
+ // ─── Project rules file (CLAUDE.md / AGENTS.md) ──────────────────────────
12
+ /**
13
+ * Read the OPENSPEC marker block from a rules file, or `null` when the file
14
+ * is missing / has no markers. Used by drift detection and update to decide
15
+ * whether a rules file needs rewriting.
16
+ */
17
+ export function readOpenSpecBlock(projectRoot, adapter) {
18
+ const dest = adapter.projectRulesPath(projectRoot);
19
+ if (!existsSync(dest))
20
+ return null;
21
+ const content = readFileSync(dest, "utf-8");
22
+ const startIdx = content.indexOf("<!-- OPENSPEC:START -->");
23
+ const endIdx = content.indexOf("<!-- OPENSPEC:END -->");
24
+ if (startIdx === -1 || endIdx === -1)
25
+ return null;
26
+ return content.slice(startIdx + "<!-- OPENSPEC:START -->".length, endIdx).trim();
27
+ }
28
+ /**
29
+ * Whether a rules file's OPENSPEC block matches the expected content.
30
+ * A missing file or absent/truncated markers counts as "does not match"
31
+ * (the caller will rewrite it), which keeps update idempotent but safe.
32
+ */
33
+ export function blockMatchesExpected(projectRoot, adapter, expected) {
34
+ const block = readOpenSpecBlock(projectRoot, adapter);
35
+ if (block === null)
36
+ return false;
37
+ return block === expected.trim();
38
+ }
39
+ /**
40
+ * Install employee-grade standards into the editor's rules file
41
+ * (CLAUDE.md for Claude, AGENTS.md for OpenCode, Cline, and Cursor). Wraps content in
42
+ * `<!-- OPENSPEC:START -->` / `<!-- OPENSPEC:END -->` markers so future
43
+ * updates can replace the block without touching the rest of the file.
44
+ */
45
+ export function installOpenSpecBlock(projectRoot, standardsContent, adapter = claudeAdapter) {
46
+ const dest = adapter.projectRulesPath(projectRoot);
47
+ const fileLabel = basename(dest);
48
+ const markerStart = "<!-- OPENSPEC:START -->";
49
+ const markerEnd = "<!-- OPENSPEC:END -->";
50
+ if (!existsSync(dest)) {
51
+ const projName = projectRoot.split("/").pop() ?? "Project";
52
+ const content = `# ${projName}\n\n${markerStart}\n\n${standardsContent.trim()}\n\n${markerEnd}\n`;
53
+ writeFileSync(dest, content);
54
+ console.log(chalk.green(` ✓ ${fileLabel}: created with employee-grade standards`));
55
+ return;
56
+ }
57
+ const existing = readFileSync(dest, "utf-8");
58
+ const hasStart = existing.includes(markerStart);
59
+ const hasEnd = existing.includes(markerEnd);
60
+ if (hasStart && hasEnd) {
61
+ const startIdx = existing.indexOf(markerStart);
62
+ const endIdx = existing.indexOf(markerEnd) + markerEnd.length;
63
+ const before = existing.slice(0, startIdx).trimEnd();
64
+ const after = existing.slice(endIdx);
65
+ const updated = before +
66
+ "\n" +
67
+ markerStart +
68
+ "\n\n" +
69
+ standardsContent.trim() +
70
+ "\n\n" +
71
+ markerEnd +
72
+ after;
73
+ writeFileSync(dest, updated);
74
+ console.log(chalk.green(` ✓ ${fileLabel}: updated employee-grade standards (markers preserved, content refreshed)`));
75
+ }
76
+ else if (!hasStart && !hasEnd) {
77
+ const updated = existing.trim() +
78
+ "\n\n" +
79
+ markerStart +
80
+ "\n\n" +
81
+ standardsContent.trim() +
82
+ "\n\n" +
83
+ markerEnd +
84
+ "\n";
85
+ writeFileSync(dest, updated);
86
+ console.log(chalk.green(` ✓ ${fileLabel}: appended employee-grade standards with markers`));
87
+ }
88
+ else {
89
+ // Incomplete markers (only START, or only END) — corrupted tool territory.
90
+ // Keep everything before the first marker (user content), discard the
91
+ // truncated tool output after it, and write a clean complete block so
92
+ // `doctor`/`update` converge instead of dead-ending on a skipped file.
93
+ const firstIdx = hasStart
94
+ ? existing.indexOf(markerStart)
95
+ : existing.indexOf(markerEnd);
96
+ const header = existing.slice(0, firstIdx).trimEnd();
97
+ const updated = header +
98
+ "\n\n" +
99
+ markerStart +
100
+ "\n\n" +
101
+ standardsContent.trim() +
102
+ "\n\n" +
103
+ markerEnd +
104
+ "\n";
105
+ writeFileSync(dest, updated);
106
+ console.log(chalk.green(` ✓ ${fileLabel}: repaired incomplete OPENSPEC markers with employee-grade standards`));
107
+ }
108
+ }
109
+ /**
110
+ * CodeGraph-first guidance prepended to the Claude wrapper so the model sees
111
+ * it in the main rules file instead of relying on the AGENTS.md import
112
+ * (imported content ranks lower and is treated as optional by the model).
113
+ */
114
+ const CODE_GRAPH_FIRST_BLOCK = `## CodeGraph 优先 🔴
115
+
116
+ 有 \`.codegraph/\` 时:结构性任务(定义/调用链/影响面/流程)默认第一步用 \`codegraph_explore\`,直接用结果回答,别先 grep/read(仅字面文本、已打开文件、结果不足时补查)。不派子 agent 重建索引。无 \`.codegraph/\` 跳过。`;
117
+ /**
118
+ * The expected OPENSPEC block content for a thin CLAUDE.md wrapper
119
+ * (CodeGraph-first guidance + workflow hint + `@AGENTS.md` import). Exported
120
+ * so drift detection / update can compare against it.
121
+ *
122
+ * The `@AGENTS.md` line is Claude Code's documented way to reuse AGENTS.md
123
+ * inside CLAUDE.md — AGENTS.md is NOT read by default ("Claude Code reads
124
+ * CLAUDE.md, not AGENTS.md"). Contract per
125
+ * https://code.claude.com/docs/en/memory.md:
126
+ * - Position is irrelevant — the doc says "@ ... anywhere in your CLAUDE.md"
127
+ * (examples even inline it mid-sentence or in a list item). The one real
128
+ * constraint: the `@` line must NOT sit inside a code span (backticks) or
129
+ * a fenced code block — the resolver skips those. This wrapper keeps the
130
+ * import at the end of the block as a bare line.
131
+ * - The path resolves relative to the importing CLAUDE.md; import recursion
132
+ * is capped at 4 hops.
133
+ * - Block-level HTML comments (`<!-- ... -->`) are stripped before context
134
+ * injection, so the OPENSPEC markers vanish while the live `@AGENTS.md`
135
+ * line inside them is still honored.
136
+ */
137
+ export function claudeWrapperStandardsContent() {
138
+ return `${CODE_GRAPH_FIRST_BLOCK}\n\n**工作流**:优先使用 OpenSpec 工作流(/opsx 命令),而非 plan mode。\n\n@AGENTS.md\n`;
139
+ }
140
+ /**
141
+ * Install a thin CLAUDE.md that imports AGENTS.md.
142
+ *
143
+ * Uses the same OPENSPEC:START/END markers as the full standards block so
144
+ * `cleanProjectRules` can remove it uniformly. The CodeGraph-first block is
145
+ * written directly into CLAUDE.md (before the @AGENTS.md import) so Claude
146
+ * Code picks it up without depending on the import.
147
+ *
148
+ * Also handles migration: if CLAUDE.md has an existing OPENSPEC:START block
149
+ * (old format that wrote standards directly to CLAUDE.md), calling
150
+ * `installOpenSpecBlock` replaces the content with the CodeGraph block +
151
+ * `@AGENTS.md` import.
152
+ */
153
+ export function installClaudeWrapper(projectRoot) {
154
+ const dest = join(projectRoot, "CLAUDE.md");
155
+ // CLAUDE.md symlinked (typically → AGENTS.md, the officially documented
156
+ // reuse pattern): AGENTS.md itself is what Claude Code reads, and
157
+ // installProjectRules already keeps the full standards in it. Writing a
158
+ // wrapper here would overwrite them through the symlink (and the wrapper's
159
+ // @AGENTS.md import would self-reference). Skip instead.
160
+ if (existsSync(dest)) {
161
+ if (lstatSync(dest).isSymbolicLink()) {
162
+ console.log(chalk.gray(" - CLAUDE.md is a symlink to AGENTS.md — standards live there, no wrapper needed"));
163
+ return;
164
+ }
165
+ }
166
+ // No-op if our full wrapper (CodeGraph block + @AGENTS.md import) is already
167
+ // in place — content-equal, so a user edit inside the markers is detected.
168
+ // A bare @AGENTS.md without our markers (added by openspec CLI or the user)
169
+ // is left untouched — but tell the user CodeGraph-first won't be written.
170
+ if (existsSync(dest)) {
171
+ const existing = readFileSync(dest, "utf-8");
172
+ const hasMarkers = existing.includes("<!-- OPENSPEC:START -->");
173
+ if (!hasMarkers && /^@AGENTS\.md\r?$/m.test(existing)) {
174
+ console.log(chalk.yellow(" ⚠ CLAUDE.md 是裸 @AGENTS.md 导入(无 OPENSPEC 标记),CodeGraph 优先约束未写入。如需启用:删除该行后重跑 openspec-pw update。"));
175
+ return;
176
+ }
177
+ if (hasMarkers && blockMatchesExpected(projectRoot, claudeAdapter, claudeWrapperStandardsContent())) {
178
+ return;
179
+ }
180
+ }
181
+ // Delegate to installOpenSpecBlock which handles create/update/append
182
+ // with OPENSPEC:START/END markers.
183
+ installOpenSpecBlock(projectRoot, claudeWrapperStandardsContent(), claudeAdapter);
184
+ }
185
+ /**
186
+ * Route employee-grade standards into project rules files.
187
+ *
188
+ * AGENTS.md is always the single source of truth, regardless of which
189
+ * editors are detected. If Claude is in use, a thin CLAUDE.md wrapper
190
+ * with `@AGENTS.md` import is created so Claude loads AGENTS.md as
191
+ * its project rules. Cline and Cursor auto-detect AGENTS.md natively — no
192
+ * wrapper needed.
193
+ */
194
+ export function installProjectRules(projectRoot, standardsContent, detected) {
195
+ if (detected.length === 0)
196
+ return;
197
+ // AGENTS.md is always the single source of truth
198
+ installOpenSpecBlock(projectRoot, standardsContent, opencodeAdapter);
199
+ // Thin CLAUDE.md with @AGENTS.md import if Claude is in use
200
+ if (detected.some((a) => a.id === "claude")) {
201
+ installClaudeWrapper(projectRoot);
202
+ }
203
+ // Register AGENTS.md in opencode.json for OpenCode
204
+ if (detected.some((a) => a.id === "opencode") && opencodeAdapter.registerInstructions) {
205
+ const existing = readOpenCodeInstructions(projectRoot);
206
+ const next = Array.from(new Set([...(existing ?? []), "AGENTS.md"]));
207
+ opencodeAdapter.registerInstructions(projectRoot, next);
208
+ }
209
+ }
210
+ /** Remove all OpenSpec marker blocks from AGENTS.md (always) and CLAUDE.md (for claude adapter). */
211
+ export function cleanProjectRules(adapter, projectRoot) {
212
+ // AGENTS.md always has the employee standards (SSOT)
213
+ removeMarkersFromFile(join(projectRoot, "AGENTS.md"), "AGENTS.md");
214
+ // CLAUDE.md may have the wrapper import if Claude is detected
215
+ if (adapter.id === "claude") {
216
+ removeMarkersFromFile(adapter.projectRulesPath(projectRoot), basename(adapter.projectRulesPath(projectRoot)));
217
+ }
218
+ }
219
+ /** Remove OpenSpec marker blocks from a single file. Only edits within markers. */
220
+ function removeMarkersFromFile(dest, fileLabel) {
221
+ if (!existsSync(dest)) {
222
+ console.log(chalk.gray(` - ${fileLabel} not found, skipping`));
223
+ return;
224
+ }
225
+ const existing = readFileSync(dest, "utf-8");
226
+ if (!existing.includes("<!-- OPENSPEC:START -->")) {
227
+ console.log(chalk.gray(` - No OpenSpec markers found in ${fileLabel}`));
228
+ return;
229
+ }
230
+ // Remove markers and their content, consuming surrounding whitespace.
231
+ // Then collapse runs of 3+ blank lines to at most 2 for a clean result.
232
+ let updated = existing.replace(/\s*<!-- OPENSPEC:START -->[\s\S]*?<!-- OPENSPEC:END -->\s*/g, "\n\n").replace(/\n{3,}/g, "\n\n").trim();
233
+ // Delete empty file rather than leaving a ghost.
234
+ if (updated === "") {
235
+ rmSync(dest);
236
+ console.log(chalk.green(` ✓ Removed empty ${fileLabel}`));
237
+ return;
238
+ }
239
+ writeFileSync(dest, updated + "\n");
240
+ console.log(chalk.green(` ✓ Removed OpenSpec markers from ${fileLabel}`));
241
+ }
242
+ /** Read the employee-grade standards source file (empty string if missing). */
243
+ export function readEmployeeStandards(srcPath) {
244
+ return existsSync(srcPath) ? readFileSync(srcPath, "utf-8") : "";
245
+ }
246
+ //# sourceMappingURL=project-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-rules.js","sourceRoot":"","sources":["../../../src/commands/editors/project-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { CommandMeta, EditorAdapter, EditorId } from "./types.js";
2
+ export declare function getAdapter(id: EditorId): EditorAdapter | undefined;
3
+ /** All registered editors, regardless of detection. */
4
+ export declare function getAllAdapters(): EditorAdapter[];
5
+ export declare function detectAdapters(projectRoot: string, homeDir?: string): EditorAdapter[];
6
+ export declare function registerAdapter(adapter: EditorAdapter): void;
7
+ /** Slash-command hint for user-facing messages. */
8
+ export declare function slashCommandForAdapter(adapter: EditorAdapter): string;
9
+ /** Relative paths installCommand writes for this adapter + meta. */
10
+ export declare function listCommandArtifactPaths(adapter: EditorAdapter, meta: CommandMeta): string[];
11
+ /** Install the command file (and optional extraArtifacts) for one adapter. */
12
+ export declare function installCommand(adapter: EditorAdapter, meta: CommandMeta, projectRoot: string): void;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Adapter registry: the ADAPTERS array plus lookup/detection/install
3
+ * helpers. Adapters self-register on module load (each adapters/*.ts
4
+ * calls registerAdapter); this module deliberately imports no adapter so
5
+ * the dependency graph stays acyclic (adapters -> registry).
6
+ *
7
+ * Registration order is load order and MUST stay:
8
+ * claude, opencode, cline, cursor, pi, omp, dsh
9
+ * (editors.ts re-exports the adapter modules in exactly that order;
10
+ * tests/editors-tools.test.ts asserts it.)
11
+ */
12
+ import { mkdirSync, writeFileSync } from "fs";
13
+ import { dirname, resolve as pathResolve } from "path";
14
+ import chalk from "chalk";
15
+ // ─── Registry ────────────────────────────────────────────────────────────
16
+ const ADAPTERS = [
17
+ // Adapters are registered after const declarations at the bottom of this file.
18
+ ];
19
+ export function getAdapter(id) {
20
+ return ADAPTERS.find((a) => a.id === id);
21
+ }
22
+ /** All registered editors, regardless of detection. */
23
+ export function getAllAdapters() {
24
+ return [...ADAPTERS];
25
+ }
26
+ export function detectAdapters(projectRoot, homeDir) {
27
+ return ADAPTERS.filter((a) => a.detect(projectRoot, homeDir));
28
+ }
29
+ export function registerAdapter(adapter) {
30
+ ADAPTERS.push(adapter);
31
+ }
32
+ /** Slash-command hint for user-facing messages. */
33
+ export function slashCommandForAdapter(adapter) {
34
+ return adapter.id === "claude" ? "/opsx:e2e" : "/opsx-e2e";
35
+ }
36
+ /** Relative paths installCommand writes for this adapter + meta. */
37
+ export function listCommandArtifactPaths(adapter, meta) {
38
+ const paths = [adapter.commandFilePath(meta.id)];
39
+ for (const extra of adapter.extraArtifacts?.(meta) ?? []) {
40
+ paths.push(extra.relativePath);
41
+ }
42
+ return paths;
43
+ }
44
+ // ─── Install helpers ─────────────────────────────────────────────────────
45
+ /** Install the command file (and optional extraArtifacts) for one adapter. */
46
+ export function installCommand(adapter, meta, projectRoot) {
47
+ const relPath = adapter.commandFilePath(meta.id);
48
+ const absPath = pathResolve(projectRoot, relPath);
49
+ mkdirSync(dirname(absPath), { recursive: true });
50
+ writeFileSync(absPath, adapter.formatCommand(meta));
51
+ console.log(chalk.green(` ✓ ${adapter.label}: ${relPath}`));
52
+ for (const extra of adapter.extraArtifacts?.(meta) ?? []) {
53
+ const extraAbs = pathResolve(projectRoot, extra.relativePath);
54
+ mkdirSync(dirname(extraAbs), { recursive: true });
55
+ writeFileSync(extraAbs, extra.contents);
56
+ console.log(chalk.green(` ✓ ${adapter.label}: ${extra.relativePath}`));
57
+ }
58
+ }
59
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/commands/editors/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,MAAM,CAAC;AACvD,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,4EAA4E;AAE5E,MAAM,QAAQ,GAAoB;AAChC,+EAA+E;CAChF,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,EAAY;IACrC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,WAAmB,EACnB,OAAgB;IAEhB,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAsB;IACpD,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,sBAAsB,CAAC,OAAsB;IAC3D,OAAO,OAAO,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC;AAC7D,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,wBAAwB,CACtC,OAAsB,EACtB,IAAiB;IAEjB,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACzD,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4EAA4E;AAE5E,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAC5B,OAAsB,EACtB,IAAiB,EACjB,WAAmB;IAEnB,MAAM,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAClD,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC;IAE7D,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACzD,MAAM,QAAQ,GAAG,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;QAC9D,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,aAAa,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
@@ -0,0 +1,37 @@
1
+ /** Escape a value for safe inclusion in a YAML frontmatter scalar. */
2
+ export declare function escapeYamlValue(value: string): string;
3
+ /** Format tags as a YAML inline array. */
4
+ export declare function formatTagsArray(tags: string[]): string;
5
+ /**
6
+ * OpenCode slash-command names are hyphenated (`/opsx-e2e`), Claude's are
7
+ * colon-prefixed (`/opsx:e2e`). Rewrite all `/opsx:` references in a
8
+ * command body for OpenCode installation.
9
+ */
10
+ export declare function transformToHyphenCommands(text: string): string;
11
+ export interface CommandMeta {
12
+ id: string;
13
+ name: string;
14
+ description: string;
15
+ category: string;
16
+ tags: string[];
17
+ body: string;
18
+ }
19
+ /** Build the command metadata for the /opsx:e2e command. */
20
+ export declare function buildCommandMeta(body: string): CommandMeta;
21
+ export interface McpStdioServer {
22
+ command: string;
23
+ args: string[];
24
+ }
25
+ export type McpServersFile = Record<string, unknown> & {
26
+ mcpServers: Record<string, McpStdioServer>;
27
+ };
28
+ /**
29
+ * Read an MCP config file with a top-level `mcpServers` map, or null if
30
+ * missing/unparseable. Preserves unknown top-level fields.
31
+ */
32
+ export declare function readMcpServersFile(configPath: string): McpServersFile | null;
33
+ /** Write an MCP config file, creating parent directories if needed. */
34
+ export declare function writeMcpServersFile(configPath: string, config: McpServersFile): void;
35
+ export declare function isMcpServerInFile(configPath: string, serverName: string): boolean;
36
+ export declare function installMcpServerInFile(configPath: string, serverName: string, command: string[]): void;
37
+ export declare function removeMcpServerFromFile(configPath: string, serverName: string): void;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Cross-editor shared helpers: YAML frontmatter escaping, command body
3
+ * transforms, command metadata, and the mcpServers JSON file family
4
+ * (Cline / Cursor / Oh My Pi).
5
+ */
6
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
7
+ import { dirname } from "path";
8
+ // ─── YAML helpers (shared by Claude frontmatter and elsewhere) ───────────
9
+ /** Escape a value for safe inclusion in a YAML frontmatter scalar. */
10
+ export function escapeYamlValue(value) {
11
+ const needsQuoting = /[:\n\r#{}[\],&*!|>'"%@`]|^\s|\s$/.test(value);
12
+ if (needsQuoting) {
13
+ const escaped = value
14
+ .replace(/\\/g, "\\\\")
15
+ .replace(/"/g, '\\"')
16
+ .replace(/\n/g, "\\n");
17
+ return `"${escaped}"`;
18
+ }
19
+ return value;
20
+ }
21
+ /** Format tags as a YAML inline array. */
22
+ export function formatTagsArray(tags) {
23
+ return `[${tags.map((t) => escapeYamlValue(t)).join(", ")}]`;
24
+ }
25
+ // ─── Body transform ──────────────────────────────────────────────────────
26
+ /**
27
+ * OpenCode slash-command names are hyphenated (`/opsx-e2e`), Claude's are
28
+ * colon-prefixed (`/opsx:e2e`). Rewrite all `/opsx:` references in a
29
+ * command body for OpenCode installation.
30
+ */
31
+ export function transformToHyphenCommands(text) {
32
+ return text.replace(/\/opsx:/g, "/opsx-");
33
+ }
34
+ /** Build the command metadata for the /opsx:e2e command. */
35
+ export function buildCommandMeta(body) {
36
+ return {
37
+ id: "e2e",
38
+ name: "OPSX: E2E",
39
+ description: "Run Playwright E2E verification for an OpenSpec change",
40
+ category: "OpenSpec",
41
+ tags: ["openspec", "playwright", "e2e", "testing"],
42
+ body,
43
+ };
44
+ }
45
+ /**
46
+ * Read an MCP config file with a top-level `mcpServers` map, or null if
47
+ * missing/unparseable. Preserves unknown top-level fields.
48
+ */
49
+ export function readMcpServersFile(configPath) {
50
+ if (!existsSync(configPath))
51
+ return null;
52
+ try {
53
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
54
+ if (raw.mcpServers && typeof raw.mcpServers === "object") {
55
+ return raw;
56
+ }
57
+ raw.mcpServers = {};
58
+ return raw;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Write an MCP config file, creating parent directories if needed. */
65
+ export function writeMcpServersFile(configPath, config) {
66
+ mkdirSync(dirname(configPath), { recursive: true });
67
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
68
+ }
69
+ export function isMcpServerInFile(configPath, serverName) {
70
+ const config = readMcpServersFile(configPath);
71
+ if (!config)
72
+ return false;
73
+ return config.mcpServers[serverName] !== undefined;
74
+ }
75
+ export function installMcpServerInFile(configPath, serverName, command) {
76
+ const config = readMcpServersFile(configPath) ?? { mcpServers: {} };
77
+ config.mcpServers[serverName] = {
78
+ command: command[0],
79
+ args: command.slice(1),
80
+ };
81
+ writeMcpServersFile(configPath, config);
82
+ }
83
+ export function removeMcpServerFromFile(configPath, serverName) {
84
+ const config = readMcpServersFile(configPath);
85
+ if (!config)
86
+ return;
87
+ if (config.mcpServers[serverName] === undefined)
88
+ return;
89
+ delete config.mcpServers[serverName];
90
+ writeMcpServersFile(configPath, config);
91
+ }
92
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared.js","sourceRoot":"","sources":["../../../src/commands/editors/shared.ts"],"names":[],"mappings":"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"}