@bossmissing/agent-meta 0.1.0 → 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.
Files changed (50) hide show
  1. package/README.md +61 -2
  2. package/assets/skills/deploy-ansible/deploy-ansible/SKILL.md +71 -0
  3. package/assets/skills/deploy-cloudflare/deploy-cloudflare/SKILL.md +51 -0
  4. package/assets/skills/deploy-github-actions/deploy-github-actions/SKILL.md +50 -0
  5. package/assets/skills/ironbank/ironbank-citations/SKILL.md +197 -0
  6. package/assets/skills/ironbank/ironbank-docs-research/SKILL.md +205 -0
  7. package/assets/skills/ironbank/ironbank-mcp-reference/SKILL.md +414 -0
  8. package/assets/skills/ironbank/ironbank-note-links/SKILL.md +87 -0
  9. package/dist/cli.js +74 -2
  10. package/dist/commands/config.d.ts +5 -0
  11. package/dist/commands/config.js +50 -0
  12. package/dist/commands/doctor.d.ts +14 -0
  13. package/dist/commands/doctor.js +187 -0
  14. package/dist/commands/init.d.ts +4 -0
  15. package/dist/commands/init.js +57 -1
  16. package/dist/commands/new-adr.js +2 -1
  17. package/dist/commands/new-change.d.ts +12 -0
  18. package/dist/commands/new-change.js +125 -0
  19. package/dist/commands/new-domain.js +2 -0
  20. package/dist/commands/preflight.d.ts +14 -0
  21. package/dist/commands/preflight.js +103 -0
  22. package/dist/commands/review.d.ts +18 -0
  23. package/dist/commands/review.js +102 -0
  24. package/dist/commands/sync.js +11 -2
  25. package/dist/core/checklist.d.ts +31 -0
  26. package/dist/core/checklist.js +43 -0
  27. package/dist/core/deploy-packs.d.ts +11 -0
  28. package/dist/core/deploy-packs.js +42 -0
  29. package/dist/core/doc-scopes.d.ts +18 -0
  30. package/dist/core/doc-scopes.js +55 -0
  31. package/dist/core/filesystem.js +9 -1
  32. package/dist/core/frontmatter.d.ts +7 -0
  33. package/dist/core/frontmatter.js +45 -9
  34. package/dist/core/mcp-packs.d.ts +26 -0
  35. package/dist/core/mcp-packs.js +86 -0
  36. package/dist/generators/adr-generator.js +3 -1
  37. package/dist/generators/hook-generator.d.ts +14 -0
  38. package/dist/generators/hook-generator.js +55 -0
  39. package/dist/generators/index-generator.d.ts +2 -2
  40. package/dist/generators/index-generator.js +15 -8
  41. package/dist/generators/mcp-generator.d.ts +9 -0
  42. package/dist/generators/mcp-generator.js +68 -0
  43. package/dist/schemas/config.schema.d.ts +0 -10
  44. package/dist/schemas/config.schema.js +0 -2
  45. package/dist/templates/index.d.ts +7 -2
  46. package/dist/templates/index.js +125 -9
  47. package/dist/validators/adr-validator.js +4 -4
  48. package/dist/validators/domain-validator.js +30 -1
  49. package/dist/validators/openspec-validator.js +33 -4
  50. package/package.json +9 -15
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import YAML from "yaml";
4
+ import { EXIT_CODES, MetaError } from "../core/errors.js";
5
+ import { detectPackageManager } from "../commands/init.js";
6
+ /** The `meta check` invocation appropriate for the project's package manager. */
7
+ export function metaCheckCommand(root) {
8
+ switch (detectPackageManager(root)) {
9
+ case "pnpm":
10
+ return "pnpm exec meta check";
11
+ case "yarn":
12
+ return "yarn exec meta check";
13
+ case "npm":
14
+ return "npx meta check";
15
+ default:
16
+ return "meta check";
17
+ }
18
+ }
19
+ const LEFTHOOK_FILENAMES = ["lefthook.yml", "lefthook.yaml"];
20
+ /**
21
+ * Create lefthook.yml with a pre-commit meta-check command, or add the
22
+ * command to an existing file. An existing meta-check entry is never
23
+ * overwritten.
24
+ */
25
+ export function generateLefthookConfig(root, dryRun) {
26
+ const existing = LEFTHOOK_FILENAMES.find((f) => fs.existsSync(path.join(root, f)));
27
+ const rel = existing ?? "lefthook.yml";
28
+ const abs = path.join(root, rel);
29
+ const run = metaCheckCommand(root);
30
+ let doc = {};
31
+ if (existing) {
32
+ try {
33
+ doc = YAML.parse(fs.readFileSync(abs, "utf8")) ?? {};
34
+ }
35
+ catch (e) {
36
+ throw new MetaError(`Cannot parse ${rel}: ${e.message}`, EXIT_CODES.CONFIG_UNPARSABLE);
37
+ }
38
+ }
39
+ if (typeof doc["pre-commit"] !== "object" || doc["pre-commit"] === null) {
40
+ doc["pre-commit"] = {};
41
+ }
42
+ const preCommit = doc["pre-commit"];
43
+ if (typeof preCommit["commands"] !== "object" || preCommit["commands"] === null) {
44
+ preCommit["commands"] = {};
45
+ }
46
+ const commands = preCommit["commands"];
47
+ const current = commands["meta-check"];
48
+ if (current) {
49
+ return { action: "unchanged", path: rel, command: current.run ?? "" };
50
+ }
51
+ commands["meta-check"] = { run };
52
+ if (!dryRun)
53
+ fs.writeFileSync(abs, YAML.stringify(doc), "utf8");
54
+ return { action: existing ? "updated" : "created", path: rel, command: run };
55
+ }
@@ -1,6 +1,6 @@
1
1
  import type { MetaConfig } from "../schemas/config.schema.js";
2
2
  import type { FileWriter } from "../core/filesystem.js";
3
3
  export declare function buildDomainIndexTable(root: string, config: MetaConfig): string;
4
- export declare function buildAdrIndexTable(root: string, config: MetaConfig): string;
4
+ export declare function buildAdrIndexTable(root: string, config: MetaConfig, relDir?: string): string;
5
5
  export declare function updateDomainIndex(writer: FileWriter, root: string, config: MetaConfig): void;
6
- export declare function updateAdrIndex(writer: FileWriter, root: string, config: MetaConfig): void;
6
+ export declare function updateAdrIndex(writer: FileWriter, root: string, config: MetaConfig, relDir?: string): void;
@@ -5,15 +5,22 @@ import { dateValueToString } from "../core/paths.js";
5
5
  import { listAdrFiles } from "./adr-generator.js";
6
6
  import { renderNamedTemplate } from "../core/templates.js";
7
7
  import { todayISO } from "../core/paths.js";
8
+ import { EXIT_CODES, MetaError } from "../core/errors.js";
8
9
  const DOMAIN_MARKERS = ["<!-- meta:domain-index:start -->", "<!-- meta:domain-index:end -->"];
9
10
  const ADR_MARKERS = ["<!-- meta:adr-index:start -->", "<!-- meta:adr-index:end -->"];
10
- function replaceBetweenMarkers(content, [start, end], body) {
11
+ function replaceBetweenMarkers(content, [start, end], body, relPath) {
11
12
  const startIdx = content.indexOf(start);
12
13
  const endIdx = content.indexOf(end);
13
- if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) {
14
+ if (startIdx === -1 && endIdx === -1) {
14
15
  // No markers: append a managed section at the end.
15
16
  return `${content.trimEnd()}\n\n${start}\n${body}\n${end}\n`;
16
17
  }
18
+ if (startIdx === -1 || endIdx === -1 || endIdx < startIdx) {
19
+ // Exactly one marker (or reversed order): regenerating here could swallow
20
+ // user content between the orphan marker and a freshly appended section.
21
+ throw new MetaError(`Index markers in ${relPath} are malformed: expected '${start}' followed by '${end}'. ` +
22
+ `Restore both markers (in order), or remove both to let the index be re-appended.`, EXIT_CODES.CHECK_FAILED);
23
+ }
17
24
  return (content.slice(0, startIdx + start.length) + "\n" + body + "\n" + content.slice(endIdx));
18
25
  }
19
26
  function frontmatterStr(data, key) {
@@ -52,8 +59,8 @@ export function buildDomainIndexTable(root, config) {
52
59
  ...rows,
53
60
  ].join("\n");
54
61
  }
55
- export function buildAdrIndexTable(root, config) {
56
- const adrDir = path.join(root, config.paths.adr_docs);
62
+ export function buildAdrIndexTable(root, config, relDir = config.paths.adr_docs) {
63
+ const adrDir = path.join(root, relDir);
57
64
  const rows = listAdrFiles(adrDir).map((info) => {
58
65
  const doc = parseDocFile(info.file);
59
66
  const title = frontmatterStr(doc.data, "title") || info.slug;
@@ -74,14 +81,14 @@ function updateIndexFile(writer, relPath, markers, table, templateName, root, co
74
81
  date: todayISO(),
75
82
  });
76
83
  }
77
- const updated = replaceBetweenMarkers(content, markers, table);
84
+ const updated = replaceBetweenMarkers(content, markers, table, relPath);
78
85
  writer.write(relPath, updated, { overwrite: true });
79
86
  }
80
87
  export function updateDomainIndex(writer, root, config) {
81
88
  const rel = path.join(config.paths.domain_docs, "README.md");
82
89
  updateIndexFile(writer, rel, DOMAIN_MARKERS, buildDomainIndexTable(root, config), "domain-index-readme.md", root, config);
83
90
  }
84
- export function updateAdrIndex(writer, root, config) {
85
- const rel = path.join(config.paths.adr_docs, "README.md");
86
- updateIndexFile(writer, rel, ADR_MARKERS, buildAdrIndexTable(root, config), "adr-index-readme.md", root, config);
91
+ export function updateAdrIndex(writer, root, config, relDir = config.paths.adr_docs) {
92
+ const rel = path.join(relDir, "README.md");
93
+ updateIndexFile(writer, rel, ADR_MARKERS, buildAdrIndexTable(root, config, relDir), "adr-index-readme.md", root, config);
87
94
  }
@@ -0,0 +1,9 @@
1
+ import type { FileWriter } from "../core/filesystem.js";
2
+ import { type McpPack } from "../core/mcp-packs.js";
3
+ /**
4
+ * Install a tool pack into the target repository:
5
+ * - merge the MCP server into `.mcp.json` (existing servers are never touched)
6
+ * - merge plugin settings into `.claude/settings.json` (existing values always win)
7
+ * - copy the pack's skills into `.claude/skills/` (existing files are skipped)
8
+ */
9
+ export declare function installMcpPack(writer: FileWriter, root: string, pack: McpPack): void;
@@ -0,0 +1,68 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { EXIT_CODES, MetaError } from "../core/errors.js";
4
+ import { listPackSkillFiles } from "../core/mcp-packs.js";
5
+ const MCP_JSON = ".mcp.json";
6
+ const CLAUDE_SETTINGS = ".claude/settings.json";
7
+ function isPlainObject(v) {
8
+ return typeof v === "object" && v !== null && !Array.isArray(v);
9
+ }
10
+ /** Deep-merge source into target, adding missing keys only — existing values always win. */
11
+ function mergeMissing(target, source) {
12
+ let changed = false;
13
+ for (const [key, value] of Object.entries(source)) {
14
+ if (!(key in target)) {
15
+ target[key] = value;
16
+ changed = true;
17
+ }
18
+ else if (isPlainObject(target[key]) && isPlainObject(value)) {
19
+ changed = mergeMissing(target[key], value) || changed;
20
+ }
21
+ // Existing non-object values are kept as-is.
22
+ }
23
+ return changed;
24
+ }
25
+ function readJson(root, rel) {
26
+ const abs = path.join(root, rel);
27
+ if (!fs.existsSync(abs))
28
+ return {};
29
+ try {
30
+ return JSON.parse(fs.readFileSync(abs, "utf8")) ?? {};
31
+ }
32
+ catch (e) {
33
+ throw new MetaError(`Cannot parse ${rel}: ${e.message}`, EXIT_CODES.CONFIG_UNPARSABLE);
34
+ }
35
+ }
36
+ /**
37
+ * Install a tool pack into the target repository:
38
+ * - merge the MCP server into `.mcp.json` (existing servers are never touched)
39
+ * - merge plugin settings into `.claude/settings.json` (existing values always win)
40
+ * - copy the pack's skills into `.claude/skills/` (existing files are skipped)
41
+ */
42
+ export function installMcpPack(writer, root, pack) {
43
+ if (pack.server) {
44
+ const doc = readJson(root, MCP_JSON);
45
+ if (!isPlainObject(doc.mcpServers))
46
+ doc.mcpServers = {};
47
+ const servers = doc.mcpServers;
48
+ if (servers[pack.name]) {
49
+ writer.actions.push({ type: "skip", path: MCP_JSON });
50
+ }
51
+ else {
52
+ servers[pack.name] = pack.server;
53
+ writer.write(MCP_JSON, JSON.stringify(doc, null, 2) + "\n", { overwrite: true });
54
+ }
55
+ }
56
+ if (pack.settings) {
57
+ const doc = readJson(root, CLAUDE_SETTINGS);
58
+ if (mergeMissing(doc, pack.settings)) {
59
+ writer.write(CLAUDE_SETTINGS, JSON.stringify(doc, null, 2) + "\n", { overwrite: true });
60
+ }
61
+ else {
62
+ writer.actions.push({ type: "skip", path: CLAUDE_SETTINGS });
63
+ }
64
+ }
65
+ for (const [rel, srcAbs] of listPackSkillFiles(pack)) {
66
+ writer.write(path.join(".claude", "skills", rel), fs.readFileSync(srcAbs, "utf8"));
67
+ }
68
+ }
@@ -38,20 +38,14 @@ export declare const configSchema: z.ZodObject<{
38
38
  templates?: string | undefined;
39
39
  }>>;
40
40
  defaults: z.ZodDefault<z.ZodObject<{
41
- doc_status: z.ZodDefault<z.ZodString>;
42
- review_interval_days: z.ZodDefault<z.ZodNumber>;
43
41
  adr_padding: z.ZodDefault<z.ZodNumber>;
44
42
  agents_max_lines: z.ZodDefault<z.ZodNumber>;
45
43
  local_agents_max_lines: z.ZodDefault<z.ZodNumber>;
46
44
  }, "strip", z.ZodTypeAny, {
47
- doc_status: string;
48
- review_interval_days: number;
49
45
  adr_padding: number;
50
46
  agents_max_lines: number;
51
47
  local_agents_max_lines: number;
52
48
  }, {
53
- doc_status?: string | undefined;
54
- review_interval_days?: number | undefined;
55
49
  adr_padding?: number | undefined;
56
50
  agents_max_lines?: number | undefined;
57
51
  local_agents_max_lines?: number | undefined;
@@ -141,8 +135,6 @@ export declare const configSchema: z.ZodObject<{
141
135
  templates: string;
142
136
  };
143
137
  defaults: {
144
- doc_status: string;
145
- review_interval_days: number;
146
138
  adr_padding: number;
147
139
  agents_max_lines: number;
148
140
  local_agents_max_lines: number;
@@ -184,8 +176,6 @@ export declare const configSchema: z.ZodObject<{
184
176
  templates?: string | undefined;
185
177
  } | undefined;
186
178
  defaults?: {
187
- doc_status?: string | undefined;
188
- review_interval_days?: number | undefined;
189
179
  adr_padding?: number | undefined;
190
180
  agents_max_lines?: number | undefined;
191
181
  local_agents_max_lines?: number | undefined;
@@ -33,8 +33,6 @@ export const configSchema = z.object({
33
33
  .default({}),
34
34
  defaults: z
35
35
  .object({
36
- doc_status: z.string().default("active"),
37
- review_interval_days: z.number().default(180),
38
36
  adr_padding: z.number().int().min(1).default(4),
39
37
  agents_max_lines: z.number().default(180),
40
38
  local_agents_max_lines: z.number().default(120),
@@ -4,9 +4,9 @@
4
4
  * paths.templates). Only {{variable}} substitution is supported.
5
5
  */
6
6
  export declare const BUILTIN_TEMPLATES: {
7
- readonly "agents.md": "# AGENTS.md\n\n## Project Overview\n\n- Project type: {{project_type}}\n- Package manager: {{package_manager}}\n- Main application paths: {{main_paths}}\n\n## Important Documentation\n\n- Architecture: `docs/architecture/`\n- Domain rules: `docs/domain/`\n- API conventions: `docs/api/`\n- Engineering rules: `docs/engineering/`\n- Architecture decisions: `docs/adr/`\n- Product capability specs: `openspec/specs/`\n- Active changes: `openspec/changes/`\n\n## Working Rules\n\n- Read the nearest applicable `AGENTS.md` before modifying files.\n- Read related domain documentation before changing business behavior.\n- Do not duplicate domain rules in this file.\n- Update OpenSpec specs when externally observable behavior changes.\n- Add or update tests for behavior changes.\n- Do not mix unrelated refactors into a feature change.\n\n## High-Risk Areas\n\nBefore changing payment, order, commission, settlement, withdrawal, permissions, webhooks, inventory, or audit logging, read the applicable documentation under `docs/domain/`.\n\n## Definition of Done\n\n- Relevant tests pass.\n- Type checking and lint pass where configured.\n- Behavior changes include documentation or specification updates where applicable.\n- High-risk changes include rollback and failure-path consideration.\n";
7
+ readonly "agents.md": "# AGENTS.md\n\n## Project Overview\n\n- Project type: {{project_type}}\n- Package manager: {{package_manager}}\n- Main application paths: {{main_paths}}\n\n## Important Documentation\n\n- Architecture: `docs/architecture/`\n- Domain rules: `docs/domain/`\n- API conventions: `docs/api/`\n- Engineering rules: `docs/engineering/`\n- Architecture decisions: `docs/adr/`\n- Product capability specs: `openspec/specs/`\n- Active changes: `openspec/changes/`\n\n## Working Rules\n\n- Read the nearest applicable `AGENTS.md` before modifying files.\n- Read related domain documentation before changing business behavior.\n- Do not modify third-party or vendored code (`node_modules/`, `vendor/`, `third_party/`, `patches/`). Wrap or extend it instead; if patching is unavoidable, record an ADR first.\n- Do not use user-level (global) skills, plugins, or MCP tools unless the human explicitly asks for them. Prefer tools, scripts, and skills defined inside this repository.\n- Never violate declared hard constraints (`docs/domain/*/rules.md` 硬限制 sections and `docs/engineering/constraints.md`). If a limit is not declared, ask the human instead of assuming it is negotiable.\n- If you are guessing, fabricating, or cannot verify a conclusion, stop and ask the human.\n- Do not duplicate domain rules in this file.\n- Update OpenSpec specs when externally observable behavior changes.\n- Add or update tests for behavior changes.\n- Do not mix unrelated refactors into a feature change.\n{{extra_rules}}\n## High-Risk Areas\n\nBefore changing payment, order, commission, settlement, withdrawal, permissions, webhooks, inventory, or audit logging, read the applicable documentation under `docs/domain/`. Hard constraints for these areas are declared in each domain's `rules.md` under 硬限制(不可违反).\n\n## Definition of Done\n\n- Relevant tests pass.\n- Type checking and lint pass where configured.\n- A human has reviewed the change.\n- All declared hard constraints hold.\n- Behavior changes include documentation or specification updates where applicable.\n- High-risk changes include rollback and failure-path consideration.\n";
8
8
  readonly "domain-readme.md": "---\ntitle: {{domain_title}}\nstatus: active\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# {{domain_title}}\n\n## 职责\n\n<!-- 描述该领域负责的业务能力。 -->\n\n## 非职责\n\n<!-- 明确该领域不负责什么,避免模块边界扩散。 -->\n\n## 核心对象\n\n| 对象 | 说明 |\n|---|---|\n| | |\n\n## 上游依赖\n\n<!-- 哪些模块、事件、服务会驱动该领域。 -->\n\n## 下游影响\n\n<!-- 该领域变化会影响哪些模块、数据、接口或用户角色。 -->\n\n## 关键规则入口\n\n- [业务规则](./rules.md)\n{{state_machine_link}}{{glossary_link}}\n\n## 关联 ADR\n\n<!-- 例如:../adr/0004-withdrawal-idempotency.md -->\n";
9
- readonly "domain-rules.md": "---\ntitle: {{domain_title}}业务规则\nstatus: active\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# {{domain_title}}业务规则\n\n## 术语\n\n<!-- 仅列出该领域特有术语;通用术语应放到 docs/domain/glossary.md。 -->\n\n## 不变量\n\n<!-- 无论何时都必须成立的规则。 -->\n\n-\n-\n-\n\n## 权限规则\n\n| 角色 | 可执行操作 | 限制 |\n|---|---|---|\n| | | |\n\n## 边界条件\n\n<!-- 写清重复请求、失败重试、并发、撤销、退款、异常状态等行为。 -->\n\n## 审计与追踪\n\n<!-- 是否需要审计日志、操作人、时间、状态变更原因等。 -->\n\n## 禁止行为\n\n-\n-\n";
9
+ readonly "domain-rules.md": "---\ntitle: {{domain_title}}业务规则\nstatus: active\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# {{domain_title}}业务规则\n\n## 术语\n\n<!-- 仅列出该领域特有术语;通用术语应放到 docs/domain/glossary.md。 -->\n\n## 硬限制(不可违反)\n\n<!-- 安全、数据一致性、API 契约、性能预算、兼容性、合规。 -->\n<!-- 没写在这里的限制,AI 会当成可协商的。 -->\n\n-\n-\n-\n\n## 软限制(可权衡)\n\n<!-- 代码风格偏好、非关键路径的实现细节、可后续优化的部分。 -->\n\n-\n\n## 权限规则\n\n| 角色 | 可执行操作 | 限制 |\n|---|---|---|\n| | | |\n\n## 边界条件\n\n<!-- 写清重复请求、失败重试、并发、撤销、退款、异常状态等行为。 -->\n\n## 审计与追踪\n\n<!-- 是否需要审计日志、操作人、时间、状态变更原因等。 -->\n";
10
10
  readonly "domain-state-machine.md": "---\ntitle: {{domain_title}}状态机\nstatus: active\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# {{domain_title}}状态机\n\n## 状态列表\n\n| 状态 | 含义 | 是否终态 |\n|---|---|---|\n| | | 否 |\n\n## 状态流转\n\n```mermaid\nstateDiagram-v2\n [*] --> pending\n pending --> completed\n pending --> cancelled\n```\n\n## 流转规则\n\n| 当前状态 | 触发事件 | 下一个状态 | 前置条件 | 副作用 |\n| ---- | ---- | ----- | ---- | --- |\n| | | | | |\n\n## 幂等规则\n\n<!-- 对重复请求、重复事件、重复回调的处理规则。 -->\n";
11
11
  readonly "domain-glossary.md": "---\ntitle: {{domain_title}}术语表\nstatus: active\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# {{domain_title}}术语表\n\n| 术语 | 定义 | 备注 |\n|---|---|---|\n| | | |\n";
12
12
  readonly "domain-agents.md": "# {{domain_title}} Module Rules\n\n## Scope\n\nThis directory contains the {{domain_title}} domain.\n\n## Required Reading\n\n- `README.md`\n- `rules.md`\n{{state_machine_reading}}\n\n## Rules for Agents\n\n- Do not change domain behavior without updating the relevant domain documentation.\n- Do not introduce new states without updating `state-machine.md`.\n- Preserve existing audit and idempotency behavior.\n- Keep business rules out of controllers and UI components.\n- Add tests for normal, boundary, and retry scenarios.\n\n## Review Checklist\n\n- Are permissions affected?\n- Are money, inventory, or externally triggered callbacks affected?\n- Is retry or duplicate handling required?\n- Does this change require an ADR?\n";
@@ -18,6 +18,11 @@ export declare const BUILTIN_TEMPLATES: {
18
18
  readonly "system-overview.md": "---\ntitle: 系统架构总览\nstatus: draft\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# 系统架构总览\n\n## 系统边界\n\n<!-- 系统包含哪些应用 / 服务。 -->\n\n## 模块划分\n\n<!-- 主要模块与职责。 -->\n\n## 关键依赖\n\n<!-- 数据库、消息队列、第三方服务。 -->\n";
19
19
  readonly "api-conventions.md": "---\ntitle: API 约定\nstatus: draft\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# API 约定\n\n## 命名与版本\n\n## 错误码规范\n\n## 鉴权约定\n";
20
20
  readonly "engineering-testing.md": "---\ntitle: 测试规范\nstatus: draft\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# 测试规范\n\n## 必须覆盖的场景\n\n- 正常路径\n- 边界条件\n- 重试与幂等\n\n## 工具与命令\n";
21
+ readonly "engineering-constraints.md": "---\ntitle: 项目级硬限制\nstatus: draft\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# 项目级硬限制\n\n<!-- 跨领域的硬限制放这里;单个领域的硬限制放 docs/domain/<domain>/rules.md。 -->\n\n## 硬限制(不可违反)\n\n<!-- 类别参考:安全、数据一致性、API 契约、性能预算、兼容性、合规。 -->\n<!-- 没写在这里的限制,AI 会当成可协商的。 -->\n\n-\n-\n\n## 软限制(可权衡)\n\n<!-- 代码风格偏好、非关键路径的实现细节、可后续优化的部分。 -->\n\n-\n";
21
22
  readonly "runbooks-readme.md": "---\ntitle: Runbooks\nstatus: draft\nowner: {{owner}}\nlast_reviewed: {{date}}\nsource_of_truth: true\napplies_to: []\n---\n\n# Runbooks\n\n<!-- 运维手册与故障处理流程。 -->\n";
23
+ readonly "change-proposal.md": "# {{change_title}}\n\n## Why\n\n<!-- 为什么要做这个变更?解决什么问题? -->\n\n## What Changes\n\n<!-- 变更内容概述:新增/修改/移除哪些能力。 -->\n\n## Impact\n\n- Affected domains: {{domain}}\n- Affected specs:\n- Affected code:\n";
24
+ readonly "change-design.md": "# {{change_title}} — Design\n\n## Context\n\n<!-- 现状与约束。 -->\n\n## Goals / Non-Goals\n\n- Goals:\n- Non-Goals:\n\n## Decisions\n\n<!-- 关键技术决策;长期决策请沉淀为 ADR(meta new-adr)。 -->\n\n## Hard Constraints(硬限制核对)\n\n<!-- 列出本变更涉及的已声明硬限制与核对结论。 -->\n<!-- 来源:docs/domain/<domain>/rules.md 硬限制章节、docs/engineering/constraints.md。 -->\n\n| 硬限制 | 来源 | 核对结论 |\n|---|---|---|\n| | | |\n\n## Risks / Trade-offs\n\n<!-- 风险与权衡。 -->\n";
25
+ readonly "change-tasks.md": "# {{change_title}} — Tasks\n\n## 1. Implementation\n\n- [ ] 1.1\n- [ ] 1.2\n\n## 2. Validation\n\n- [ ] 2.1 Tests updated\n- [ ] 2.2 `meta check` passes\n";
26
+ readonly "change-risk-review.md": "# {{change_title}} — Risk Review\n\n- Related domain: {{domain}}\n- Date: {{date}}\n\n## Checklist\n\n- [ ] Money representation(金额表示与精度)\n- [ ] Idempotency(幂等)\n- [ ] Retry behavior(重试行为)\n- [ ] Callback deduplication(回调去重)\n- [ ] Audit logging(审计日志)\n- [ ] Permission boundaries(权限边界)\n- [ ] Rollback plan(回滚方案)\n- [ ] Declared hard constraints checked(已声明的硬限制全部核对)\n\n## Notes\n\n<!-- 每项勾选前写清结论或“不适用”的原因。 -->\n";
22
27
  };
23
28
  export type TemplateName = keyof typeof BUILTIN_TEMPLATES;
@@ -25,19 +25,25 @@ const AGENTS_MD = `# AGENTS.md
25
25
 
26
26
  - Read the nearest applicable \`AGENTS.md\` before modifying files.
27
27
  - Read related domain documentation before changing business behavior.
28
+ - Do not modify third-party or vendored code (\`node_modules/\`, \`vendor/\`, \`third_party/\`, \`patches/\`). Wrap or extend it instead; if patching is unavoidable, record an ADR first.
29
+ - Do not use user-level (global) skills, plugins, or MCP tools unless the human explicitly asks for them. Prefer tools, scripts, and skills defined inside this repository.
30
+ - Never violate declared hard constraints (\`docs/domain/*/rules.md\` 硬限制 sections and \`docs/engineering/constraints.md\`). If a limit is not declared, ask the human instead of assuming it is negotiable.
31
+ - If you are guessing, fabricating, or cannot verify a conclusion, stop and ask the human.
28
32
  - Do not duplicate domain rules in this file.
29
33
  - Update OpenSpec specs when externally observable behavior changes.
30
34
  - Add or update tests for behavior changes.
31
35
  - Do not mix unrelated refactors into a feature change.
32
-
36
+ {{extra_rules}}
33
37
  ## High-Risk Areas
34
38
 
35
- Before changing payment, order, commission, settlement, withdrawal, permissions, webhooks, inventory, or audit logging, read the applicable documentation under \`docs/domain/\`.
39
+ Before changing payment, order, commission, settlement, withdrawal, permissions, webhooks, inventory, or audit logging, read the applicable documentation under \`docs/domain/\`. Hard constraints for these areas are declared in each domain's \`rules.md\` under 硬限制(不可违反).
36
40
 
37
41
  ## Definition of Done
38
42
 
39
43
  - Relevant tests pass.
40
44
  - Type checking and lint pass where configured.
45
+ - A human has reviewed the change.
46
+ - All declared hard constraints hold.
41
47
  - Behavior changes include documentation or specification updates where applicable.
42
48
  - High-risk changes include rollback and failure-path consideration.
43
49
  `;
@@ -98,14 +104,21 @@ applies_to: []
98
104
 
99
105
  <!-- 仅列出该领域特有术语;通用术语应放到 docs/domain/glossary.md。 -->
100
106
 
101
- ## 不变量
107
+ ## 硬限制(不可违反)
102
108
 
103
- <!-- 无论何时都必须成立的规则。 -->
109
+ <!-- 安全、数据一致性、API 契约、性能预算、兼容性、合规。 -->
110
+ <!-- 没写在这里的限制,AI 会当成可协商的。 -->
104
111
 
105
112
  -
106
113
  -
107
114
  -
108
115
 
116
+ ## 软限制(可权衡)
117
+
118
+ <!-- 代码风格偏好、非关键路径的实现细节、可后续优化的部分。 -->
119
+
120
+ -
121
+
109
122
  ## 权限规则
110
123
 
111
124
  | 角色 | 可执行操作 | 限制 |
@@ -119,11 +132,6 @@ applies_to: []
119
132
  ## 审计与追踪
120
133
 
121
134
  <!-- 是否需要审计日志、操作人、时间、状态变更原因等。 -->
122
-
123
- ## 禁止行为
124
-
125
- -
126
- -
127
135
  `;
128
136
  const DOMAIN_STATE_MACHINE = `---
129
137
  title: {{domain_title}}状态机
@@ -413,6 +421,109 @@ applies_to: []
413
421
 
414
422
  ## 工具与命令
415
423
  `;
424
+ const CHANGE_PROPOSAL = `# {{change_title}}
425
+
426
+ ## Why
427
+
428
+ <!-- 为什么要做这个变更?解决什么问题? -->
429
+
430
+ ## What Changes
431
+
432
+ <!-- 变更内容概述:新增/修改/移除哪些能力。 -->
433
+
434
+ ## Impact
435
+
436
+ - Affected domains: {{domain}}
437
+ - Affected specs:
438
+ - Affected code:
439
+ `;
440
+ const CHANGE_DESIGN = `# {{change_title}} — Design
441
+
442
+ ## Context
443
+
444
+ <!-- 现状与约束。 -->
445
+
446
+ ## Goals / Non-Goals
447
+
448
+ - Goals:
449
+ - Non-Goals:
450
+
451
+ ## Decisions
452
+
453
+ <!-- 关键技术决策;长期决策请沉淀为 ADR(meta new-adr)。 -->
454
+
455
+ ## Hard Constraints(硬限制核对)
456
+
457
+ <!-- 列出本变更涉及的已声明硬限制与核对结论。 -->
458
+ <!-- 来源:docs/domain/<domain>/rules.md 硬限制章节、docs/engineering/constraints.md。 -->
459
+
460
+ | 硬限制 | 来源 | 核对结论 |
461
+ |---|---|---|
462
+ | | | |
463
+
464
+ ## Risks / Trade-offs
465
+
466
+ <!-- 风险与权衡。 -->
467
+ `;
468
+ const CHANGE_TASKS = `# {{change_title}} — Tasks
469
+
470
+ ## 1. Implementation
471
+
472
+ - [ ] 1.1
473
+ - [ ] 1.2
474
+
475
+ ## 2. Validation
476
+
477
+ - [ ] 2.1 Tests updated
478
+ - [ ] 2.2 \`meta check\` passes
479
+ `;
480
+ const CHANGE_RISK_REVIEW = `# {{change_title}} — Risk Review
481
+
482
+ - Related domain: {{domain}}
483
+ - Date: {{date}}
484
+
485
+ ## Checklist
486
+
487
+ - [ ] Money representation(金额表示与精度)
488
+ - [ ] Idempotency(幂等)
489
+ - [ ] Retry behavior(重试行为)
490
+ - [ ] Callback deduplication(回调去重)
491
+ - [ ] Audit logging(审计日志)
492
+ - [ ] Permission boundaries(权限边界)
493
+ - [ ] Rollback plan(回滚方案)
494
+ - [ ] Declared hard constraints checked(已声明的硬限制全部核对)
495
+
496
+ ## Notes
497
+
498
+ <!-- 每项勾选前写清结论或“不适用”的原因。 -->
499
+ `;
500
+ const ENGINEERING_CONSTRAINTS = `---
501
+ title: 项目级硬限制
502
+ status: draft
503
+ owner: {{owner}}
504
+ last_reviewed: {{date}}
505
+ source_of_truth: true
506
+ applies_to: []
507
+ ---
508
+
509
+ # 项目级硬限制
510
+
511
+ <!-- 跨领域的硬限制放这里;单个领域的硬限制放 docs/domain/<domain>/rules.md。 -->
512
+
513
+ ## 硬限制(不可违反)
514
+
515
+ <!-- 类别参考:安全、数据一致性、API 契约、性能预算、兼容性、合规。 -->
516
+ <!-- 没写在这里的限制,AI 会当成可协商的。 -->
517
+
518
+ -
519
+ -
520
+
521
+ ## 软限制(可权衡)
522
+
523
+ <!-- 代码风格偏好、非关键路径的实现细节、可后续优化的部分。 -->
524
+
525
+ -
526
+ `;
416
527
  const RUNBOOKS_README = `---
417
528
  title: Runbooks
418
529
  status: draft
@@ -441,5 +552,10 @@ export const BUILTIN_TEMPLATES = {
441
552
  "system-overview.md": SYSTEM_OVERVIEW,
442
553
  "api-conventions.md": API_CONVENTIONS,
443
554
  "engineering-testing.md": ENGINEERING_TESTING,
555
+ "engineering-constraints.md": ENGINEERING_CONSTRAINTS,
444
556
  "runbooks-readme.md": RUNBOOKS_README,
557
+ "change-proposal.md": CHANGE_PROPOSAL,
558
+ "change-design.md": CHANGE_DESIGN,
559
+ "change-tasks.md": CHANGE_TASKS,
560
+ "change-risk-review.md": CHANGE_RISK_REVIEW,
445
561
  };
@@ -27,8 +27,8 @@ export function validateAdrs(ctx) {
27
27
  code: "ADR_FILENAME_INVALID",
28
28
  severity: "error",
29
29
  file: path.join(relDir, f),
30
- message: `ADR filename must match NNNN-slug.md (padding: ${config.defaults.adr_padding})`,
31
- suggestion: "Rename the file, e.g. 0002-my-decision.md.",
30
+ message: "ADR filename must match <number>-slug.md",
31
+ suggestion: `Rename the file, e.g. ${"2".padStart(config.defaults.adr_padding, "0")}-my-decision.md.`,
32
32
  });
33
33
  }
34
34
  }
@@ -87,8 +87,8 @@ export function validateAdrs(ctx) {
87
87
  });
88
88
  }
89
89
  else {
90
- const numMatch = /(\d{4,})/.exec(title);
91
- if (!numMatch || parseInt(numMatch[1], 10) !== info.number) {
90
+ const titleNumbers = title.match(/\d+/g) ?? [];
91
+ if (!titleNumbers.some((s) => parseInt(s, 10) === info.number)) {
92
92
  issues.push({
93
93
  code: "ADR_TITLE_NUMBER_MISMATCH",
94
94
  severity: "error",
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { parseDocFile, hasSection } from "../core/frontmatter.js";
3
+ import { parseDocFile, hasSection, sectionHasContent, HARD_CONSTRAINT_HEADINGS, } from "../core/frontmatter.js";
4
4
  import { matchRiskKeywords } from "../core/risk.js";
5
5
  export function listDomainDirs(root, domainDocs) {
6
6
  const abs = path.join(root, domainDocs);
@@ -99,6 +99,20 @@ export function validateDomains(ctx) {
99
99
  }
100
100
  }
101
101
  if (riskHits.length > 0) {
102
+ // Hard constraints must be declared; undeclared limits get treated as
103
+ // negotiable by AI agents.
104
+ if (fs.existsSync(rulesAbs)) {
105
+ const rules = parseDocFile(rulesAbs);
106
+ if (!sectionHasContent(rules.content, HARD_CONSTRAINT_HEADINGS)) {
107
+ issues.push({
108
+ code: "HIGH_RISK_DOMAIN_MISSING_HARD_CONSTRAINTS",
109
+ severity: "warning",
110
+ file: path.join(relDir, "rules.md"),
111
+ message: `High-risk domain '${domain}' declares no hard constraints`,
112
+ suggestion: 'Fill the "## 硬限制(不可违反)" section in rules.md (security, data consistency, API contracts, performance budgets, compatibility, compliance).',
113
+ });
114
+ }
115
+ }
102
116
  if (!fs.existsSync(rulesAbs)) {
103
117
  issues.push({
104
118
  code: "HIGH_RISK_DOMAIN_MISSING_RULES",
@@ -126,6 +140,21 @@ export function validateDomains(ctx) {
126
140
  suggestion: `Run \`meta new-domain ${domain} --force --with-state-machine\`.`,
127
141
  });
128
142
  }
143
+ // High-risk domains should link the ADRs backing their key decisions.
144
+ const docs = [readmeAbs, rulesAbs].filter((abs) => fs.existsSync(abs));
145
+ const referencesAdr = docs.some((abs) => {
146
+ const body = fs.readFileSync(abs, "utf8").replace(/<!--[\s\S]*?-->/g, "");
147
+ return /adr\//i.test(body) || /ADR-\d/.test(body);
148
+ });
149
+ if (docs.length > 0 && !referencesAdr) {
150
+ issues.push({
151
+ code: "DOMAIN_NO_ADR_REFERENCE",
152
+ severity: "warning",
153
+ file: relDir,
154
+ message: `High-risk domain '${domain}' does not reference any ADR`,
155
+ suggestion: `Record key decisions with \`meta new-adr ${domain}-idempotency\` and link them from the domain README.`,
156
+ });
157
+ }
129
158
  }
130
159
  }
131
160
  return { issues, checkedFiles };
@@ -1,5 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { matchRiskKeywords } from "../core/risk.js";
4
+ import { listDomainDirs } from "./domain-validator.js";
3
5
  export function validateOpenSpec(ctx) {
4
6
  const issues = [];
5
7
  const { root, config } = ctx;
@@ -53,24 +55,51 @@ export function validateOpenSpec(ctx) {
53
55
  });
54
56
  }
55
57
  }
56
- // Each change directory should contain at least a proposal or tasks file.
58
+ // Each change directory must contain the core change files.
57
59
  const changesAbs = path.join(openspecAbs, "changes");
58
60
  if (fs.existsSync(changesAbs)) {
59
61
  for (const entry of fs.readdirSync(changesAbs, { withFileTypes: true })) {
60
62
  if (!entry.isDirectory())
61
63
  continue;
64
+ // OpenSpec keeps completed changes under changes/archive/.
65
+ if (entry.name === "archive")
66
+ continue;
62
67
  checkedFiles++;
63
68
  const changeDir = path.join(changesAbs, entry.name);
64
- const hasCore = ["proposal.md", "tasks.md", "design.md"].some((f) => fs.existsSync(path.join(changeDir, f)));
65
- if (!hasCore) {
69
+ const missing = ["proposal.md", "tasks.md"].filter((f) => !fs.existsSync(path.join(changeDir, f)));
70
+ if (missing.length > 0) {
66
71
  issues.push({
67
72
  code: "OPENSPEC_CHANGE_INCOMPLETE",
68
73
  severity: "warning",
69
74
  file: path.join(openspecRel, "changes", entry.name),
70
- message: "Change directory has none of proposal.md / design.md / tasks.md",
75
+ message: `Change directory is missing: ${missing.join(", ")}`,
76
+ suggestion: `Run \`meta new-change ${entry.name}\` to create the missing files.`,
71
77
  });
72
78
  }
73
79
  }
74
80
  }
81
+ // High-risk domain docs should reference OpenSpec specs or changes.
82
+ for (const domain of listDomainDirs(root, config.paths.domain_docs)) {
83
+ const riskHits = matchRiskKeywords(domain, config.risk_keywords);
84
+ if (riskHits.length === 0)
85
+ continue;
86
+ const domainRel = path.join(config.paths.domain_docs, domain);
87
+ const docs = ["README.md", "rules.md"]
88
+ .map((f) => path.join(root, domainRel, f))
89
+ .filter((abs) => fs.existsSync(abs));
90
+ if (docs.length === 0)
91
+ continue;
92
+ checkedFiles += docs.length;
93
+ const referencesOpenspec = docs.some((abs) => /openspec/i.test(fs.readFileSync(abs, "utf8").replace(/<!--[\s\S]*?-->/g, "")));
94
+ if (!referencesOpenspec) {
95
+ issues.push({
96
+ code: "HIGH_RISK_DOMAIN_NO_OPENSPEC_REFERENCE",
97
+ severity: "warning",
98
+ file: domainRel,
99
+ message: `High-risk domain '${domain}' does not reference any OpenSpec specs or changes`,
100
+ suggestion: `Link the relevant openspec/specs/ or openspec/changes/ entries from ${domainRel}/README.md.`,
101
+ });
102
+ }
103
+ }
75
104
  return { issues, checkedFiles };
76
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bossmissing/agent-meta",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Agent Meta CLI - manage AI agent collaboration metadata (AGENTS.md, domain docs, ADRs, frontmatter checks)",
5
5
  "keywords": [
6
6
  "cli",
@@ -22,6 +22,7 @@
22
22
  "types": "./dist/index.d.ts",
23
23
  "files": [
24
24
  "dist",
25
+ "assets",
25
26
  "README.md",
26
27
  "LICENSE"
27
28
  ],
@@ -32,14 +33,6 @@
32
33
  "engines": {
33
34
  "node": ">=20"
34
35
  },
35
- "scripts": {
36
- "build": "tsc -p tsconfig.build.json",
37
- "dev": "tsx src/cli.ts",
38
- "test": "vitest run",
39
- "test:watch": "vitest",
40
- "typecheck": "tsc --noEmit",
41
- "prepublishOnly": "pnpm build"
42
- },
43
36
  "dependencies": {
44
37
  "@inquirer/prompts": "^7.0.0",
45
38
  "commander": "^12.1.0",
@@ -55,10 +48,11 @@
55
48
  "typescript": "^5.5.0",
56
49
  "vitest": "^2.1.0"
57
50
  },
58
- "packageManager": "pnpm@10.0.0",
59
- "pnpm": {
60
- "onlyBuiltDependencies": [
61
- "esbuild"
62
- ]
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.build.json",
53
+ "dev": "tsx src/cli.ts",
54
+ "test": "vitest run",
55
+ "test:watch": "vitest",
56
+ "typecheck": "tsc --noEmit"
63
57
  }
64
- }
58
+ }