@dotdotgod/hermes 0.6.7 → 0.7.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/mcp/cli.mjs CHANGED
@@ -760,6 +760,24 @@ var init_parser = __esm({
760
760
  }
761
761
  });
762
762
 
763
+ // packages/cli/src/graph-view/node-style.cjs
764
+ var require_node_style = __commonJS({
765
+ "packages/cli/src/graph-view/node-style.cjs"(exports, module) {
766
+ var palette = ["#b9a3ff", "#70c9a2", "#72b9e6", "#d6b879", "#e393a9", "#78c9cd", "#a6c67a", "#e5a475"];
767
+ function describeNode(path, root = "docs") {
768
+ root = root.replace(/^\.\//, "").replace(/\/$/, "");
769
+ if (!path.startsWith(`${root}/`)) return { label: path, group: null, color: null };
770
+ const parts = path.slice(root.length + 1).split("/");
771
+ const group = parts.length === 1 ? root : parts.shift();
772
+ let hash2 = 0;
773
+ for (const char of group) hash2 = Math.imul(hash2, 31) + char.charCodeAt(0) >>> 0;
774
+ const known = { docs: "#b9a3ff", spec: "#72b9e6", arch: "#d6b879", test: "#70c9a2", concept: "#e393a9", report: "#78c9cd" };
775
+ return { label: parts.join("/"), group, color: known[group] ?? palette[hash2 % palette.length] };
776
+ }
777
+ module.exports = { describeNode };
778
+ }
779
+ });
780
+
763
781
  // packages/cli/src/init.mjs
764
782
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, statSync, writeFileSync as writeFileSync2 } from "node:fs";
765
783
  import { basename, dirname as dirname2, join as join5, resolve as resolve3 } from "node:path";
@@ -1503,9 +1521,15 @@ function memoryConfigSummary(config) {
1503
1521
 
1504
1522
  // packages/cli/src/config/templates.mjs
1505
1523
  var DEFAULT_TEMPLATE_NAME = "software";
1524
+ var engineeringScaffold = [
1525
+ ["docs/spec/README.md", "Specs", "Use this area for behavior specs, API contracts, and requirements. Follow `dotdotgod validate` traceability guidance for applicable documents."],
1526
+ ["docs/arch/README.md", "Architecture", "Use this area for architecture decisions, design rationale, conventions, and integration boundaries."],
1527
+ ["docs/test/README.md", "Verification", "Use this area for test strategy, evaluation methods, regression cases, and verification records."]
1528
+ ];
1506
1529
  var BUILT_IN_TEMPLATE_SCAFFOLDS = {
1507
- software: [],
1530
+ software: engineeringScaffold,
1508
1531
  research: [
1532
+ ...engineeringScaffold,
1509
1533
  ["docs/research/README.md", "Research", "Use this area for research notes, sources, and findings."],
1510
1534
  ["docs/record/README.md", "Research Records", "Use this area for dated measurements, experiments, and execution records."],
1511
1535
  ["docs/report/README.md", "Reports", "Use this area for research diagnoses, analyses, results, and performance reports."],
@@ -1771,11 +1795,10 @@ function formatAction(item) {
1771
1795
  return `${item.status.padEnd(13)} ${item.path}${extra}`;
1772
1796
  }
1773
1797
  function parseInitOptions(argv) {
1774
- const options = { root: null, projectName: "", dotdotSetting: false, dryRun: false, json: false, template: null, documentationRoot: "docs", documentationRootExplicit: false };
1798
+ const options = { root: null, projectName: "", dryRun: false, json: false, template: null, documentationRoot: "docs", documentationRootExplicit: false };
1775
1799
  for (let i = 0; i < argv.length; i += 1) {
1776
1800
  const arg = argv[i];
1777
1801
  if (arg === "--json") options.json = true;
1778
- else if (arg === "--dotdot-setting") options.dotdotSetting = true;
1779
1802
  else if (arg === "--dry-run") options.dryRun = true;
1780
1803
  else if (arg === "--claude-plans") options.claudePlans = true;
1781
1804
  else if (arg === "--project-name" || arg === "--template" || arg === "--documentation-root") {
@@ -1854,8 +1877,7 @@ function ensureGitignoreEntry(options, entry, actions) {
1854
1877
  `);
1855
1878
  actions.push(action(existed ? "updated" : "created", target, { add: entry }));
1856
1879
  }
1857
- function agentContent(projectName, dotdotSetting) {
1858
- const dotdotAgentRule = dotdotSetting ? "\n- Follow the project documentation structure in `docs/arch/DOCS_STRUCTURE.md` and code conventions in `docs/arch/CODE_CONVENTIONS.md`." : "";
1880
+ function agentContent(projectName, domainMap) {
1859
1881
  return `# AGENTS.md
1860
1882
 
1861
1883
  Canonical instructions for AI coding agents working in this repository.
@@ -1873,7 +1895,7 @@ Canonical instructions for AI coding agents working in this repository.
1873
1895
  - Preserve user edits and unrelated dirty worktree changes.
1874
1896
  - Prefer existing local patterns over introducing new abstractions.
1875
1897
  - Update docs when behavior, architecture, or test strategy changes.
1876
- - When using the dotdotgod CLI, run \`dotdotgod validate\` after docs changes and follow its traceability guidance for behavior specs.${dotdotAgentRule}
1898
+ - When using the dotdotgod CLI, run \`dotdotgod validate\` after docs changes and follow its traceability guidance.
1877
1899
 
1878
1900
  ## dotdotgod
1879
1901
 
@@ -1898,15 +1920,12 @@ TODO
1898
1920
 
1899
1921
  ## Documentation Map
1900
1922
 
1901
- - \`docs/spec/\`: product behavior, API contracts, user-facing requirements.
1902
- - \`docs/test/\`: test strategy, regression cases, manual verification notes.
1903
- - \`docs/arch/\`: architecture decisions, code conventions, module boundaries, data flow, infrastructure/runtime dependencies, integration boundaries, and migration design.
1923
+ ${domainMap}
1904
1924
  - \`docs/\`: all directories use kebab-case; all markdown file names use UPPER_SNAKE_CASE, including \`README.md\`.
1905
1925
  - \`docs/\`: prefer keeping individual markdown files under the configured markdown validation budgets (default 200 lines and 10,000 characters); split larger docs into focused UPPER_SNAKE_CASE files and keep \`README.md\` as the index/overview unless a narrow size-check exception is configured.
1906
1926
  - \`docs/\`: when adding, renaming, splitting, moving, or archiving docs, update the nearest relevant \`README.md\` index/table of contents in the same change.
1907
1927
  - \`docs/\`: each docs subdirectory \`README.md\` acts as the local table of contents; list important files, task directories, status, and a one-line purpose for each entry.
1908
1928
  - \`docs/\`: start small with a single focused markdown file; when one domain grows into multiple docs, promote it to \`docs/<area>/<domain>/README.md\` plus related UPPER_SNAKE_CASE files in that directory.
1909
- - \`docs/arch/\`: code conventions may start as \`CODE_CONVENTIONS.md\`; when they grow across multiple topics, use \`docs/arch/conventions/README.md\` as the index with supporting UPPER_SNAKE_CASE files.
1910
1929
  - \`docs/plan/\`: local active implementation plans. Create one kebab-case directory per task (\`docs/plan/<task-slug>/\`), keep the task overview/index in that directory's \`README.md\`, and add supporting UPPER_SNAKE_CASE plan files alongside it. Ignored by git by default.
1911
1930
  - \`docs/archive/\`: local completed plans, temporary reports, historical notes, payload captures. Move completed plan task directories to \`docs/archive/plan/<task-slug>/\`; put temporary reports and investigations under \`docs/archive/report/<report-slug>/\`. Ignored by git by default.
1912
1931
 
@@ -1917,7 +1936,7 @@ TODO
1917
1936
 
1918
1937
  Keep long-lived instructions here so agent-specific files do not drift.`;
1919
1938
  }
1920
- function docsReadmeContent() {
1939
+ function docsReadmeContent(domainMap) {
1921
1940
  return `# Docs
1922
1941
 
1923
1942
  This directory keeps project knowledge close to the code.
@@ -1936,114 +1955,20 @@ This directory keeps project knowledge close to the code.
1936
1955
 
1937
1956
  ## Map
1938
1957
 
1939
- - \`spec/\`: product behavior, API contracts, user-facing requirements.
1940
- - \`test/\`: test strategy, regression cases, manual verification notes.
1941
- - \`arch/\`: architecture decisions, code conventions, module boundaries, data flow, infrastructure/runtime dependencies, integration boundaries, and migration design.
1958
+ ${domainMap.replaceAll("`docs/", "`")}
1942
1959
  - \`plan/\`: local active implementation plans. Create one kebab-case directory per task (\`plan/<task-slug>/\`), keep the task overview/index in that directory's \`README.md\`, and add supporting UPPER_SNAKE_CASE plan files alongside it. Ignored by git by default.
1943
1960
  - \`archive/\`: local completed plans, temporary reports, historical notes, payload captures. Move completed plan task directories to \`archive/plan/<task-slug>/\`; put temporary reports and investigations under \`archive/report/<report-slug>/\`. Ignored by git by default.`;
1944
1961
  }
1945
- function docsStructureContent() {
1946
- return `# Docs Structure
1947
-
1948
- Long-term documentation structure for this project.
1949
-
1950
- ## Top-Level Areas
1951
-
1952
- - \`docs/spec/\`: product behavior, API contracts, user-facing requirements, and feature contracts.
1953
- - \`docs/test/\`: test strategy, coverage notes, regression cases, and manual verification records.
1954
- - \`docs/arch/\`: architecture decisions, code conventions, module boundaries, data flow, infrastructure/runtime dependencies, integration boundaries, and migration design.
1955
- - \`docs/plan/\`: local active implementation plans.
1956
- - \`docs/archive/\`: local completed plans, historical notes, payload captures, and investigation notes.
1957
-
1958
- ## Naming
1959
-
1960
- - Directories under \`docs/\` use kebab-case.
1961
- - Markdown files under \`docs/\` use UPPER_SNAKE_CASE.
1962
- - \`README.md\` is the only mixed-case markdown filename exception and is required for index/overview files.
1963
-
1964
- ## File Size Guideline
1965
-
1966
- Prefer keeping individual markdown files under 200 lines and 10,000 characters. When either guideline is exceeded, split the document into focused UPPER_SNAKE_CASE files and keep \`README.md\` as the index/overview. Configured validation exceptions should stay narrow and intentional.
1967
-
1968
- ## README Indexes
1969
-
1970
- Each docs subdirectory \`README.md\` acts as the local table of contents. It should list important files, task directories, status, and a one-line purpose for each entry.
1971
-
1972
- When adding, renaming, splitting, moving, or archiving docs, update the nearest relevant \`README.md\` in the same change.
1973
-
1974
- ## Domain Directory Promotion
1975
-
1976
- Start small with one focused markdown file. When one domain grows into multiple docs, promote it to \`docs/<area>/<domain>/README.md\` and place related UPPER_SNAKE_CASE markdown files in that directory.
1977
-
1978
- ## Spec Writing Contract
1979
-
1980
- Behavior specs describe the current product contract: supported commands, API shapes, user-visible behavior, defaults, constraints, and validation outcomes.
1981
-
1982
- Specs should not describe how behavior changed over time. Rewrite historical-change wording into direct current-state rules. Historical context, migration rationale, future extension ideas, and completed-plan notes belong in \`docs/arch/\`, \`docs/test/\`, \`docs/archive/\`, or active \`docs/plan/\` files rather than behavior specs. If compatibility behavior is still user-visible, keep it in the spec but phrase it as a current supported or unsupported rule.
1983
-
1984
- Config/action terms such as \`remove\`, \`exclude\`, \`fallback\`, and \`replacement semantics\` are allowed when they name current behavior precisely.
1985
-
1986
- ## Traceability Blocks
1987
-
1988
- Behavior specs may include fenced \`json dotdotgod\` traceability blocks as the final section to connect specs to source, tests, related docs, and verification commands. The dotdotgod CLI owns the schema and validation behavior.
1989
-
1990
- ## Plan and Archive Directories
1991
-
1992
- Active task plans use \`docs/plan/<task-slug>/README.md\`. Completed or superseded plan task directories move to \`docs/archive/plan/<task-slug>/\`. Temporary investigations, reports, payload captures, and historical notes move to \`docs/archive/report/<report-slug>/\`.
1993
-
1994
- \`docs/plan\` and \`docs/archive\` are ignored by git by default.`;
1995
- }
1996
- function codeConventionsContent() {
1997
- return `# Code Conventions
1998
-
1999
- Dotdot code conventions for keeping implementation simple and maintainable.
2000
-
2001
- ## Abstraction Boundaries
2002
-
2003
- - Do not introduce unnecessary abstractions.
2004
- - Do not abstract code that is not reused.
2005
- - Do not abstract reused code when the reused behavior is likely to split into separate features or flows later.
2006
- - Prefer local, explicit code until a stable reuse pattern appears.
2007
-
2008
- ## Source File Size
2009
-
2010
- - Keep source files small enough to read in one focused pass by humans and coding agents.
2011
- - If code grows beyond 150 lines, consider splitting or extracting focused units even when it is not reused.
2012
- - Review files approaching 250 lines for focused extraction by responsibility.
2013
- - Split by behavior or responsibility, not by arbitrary layers.
2014
-
2015
- ## Dotdotgod Impact Hotspots
2016
-
2017
- - Treat repeated \`dotdotgod graph impact\` results that collapse onto one large file as a design signal, not as normal precision.
2018
- - Dotdotgod impact reveals mixed-responsibility hotspots; it does not replace focused module boundaries.
2019
- - When unrelated changes keep pointing to the same source file, split the file by behavior so impact results, tests, and docs can map to narrower responsibilities.
2020
-
2021
- ## Extraction and Testability
2022
-
2023
- - Prefer extracting pure helpers when behavior can be tested without runtime dependencies.
2024
- - Keep runtime integration explicit and local until reuse is stable.
2025
- - Put testable logic in focused modules before adding broad framework abstractions.
2026
- - Preserve plain-text readability: avoid dense clever code, hidden control flow, and large mixed-responsibility files.`;
2027
- }
2028
- function initFiles(options) {
2029
- const archReadmeExtra = options.dotdotSetting ? "\n\n## Index\n\n- `DOCS_STRUCTURE.md`: documentation layout, naming, README index, spec current-state writing contract, and domain directory promotion rules.\n- `CODE_CONVENTIONS.md`: dotdot code conventions, including abstraction boundaries, source file size guidance, impact hotspot handling, and extraction/testability rules. If conventions grow across multiple topics, promote them to `conventions/README.md` with supporting UPPER_SNAKE_CASE files." : "";
1962
+ function initFiles(options, scaffold) {
1963
+ const domainMap = scaffold.filter((item) => item.type === "file").map((item) => `- \`${item.path.replace(/README\.md$/, "")}\`: ${item.content.split("\n\n")[1]}`).join("\n");
2030
1964
  const files = [
2031
- ["AGENTS.md", agentContent(options.projectName, options.dotdotSetting)],
1965
+ ["AGENTS.md", agentContent(options.projectName, domainMap)],
2032
1966
  ["CLAUDE.md", "# CLAUDE.md\n\n@AGENTS.md"],
2033
1967
  ["CODEX.md", "# CODEX.md\n\nSee [AGENTS.md](./AGENTS.md)."],
2034
- ["docs/README.md", docsReadmeContent()],
2035
- ["docs/spec/README.md", "# Specs\n\nUse this area for behavior specs, API contracts, and product requirements.\n\nFor projects using the dotdotgod CLI, behavior specs may be required by `dotdotgod validate` to include fenced `json dotdotgod` traceability blocks as the final section. The CLI owns the schema and prints property-level repair guidance when validation fails."],
2036
- ["docs/test/README.md", "# Tests\n\nUse this area for test strategy, coverage notes, regression cases, and manual verification records."],
2037
- ["docs/arch/README.md", `# Architecture
2038
-
2039
- Use this area for architecture decisions, code conventions, module boundaries, data flow notes, infrastructure/runtime dependencies, integration boundaries, and migration design.${archReadmeExtra}`],
1968
+ ["docs/README.md", docsReadmeContent(domainMap)],
2040
1969
  ["docs/plan/README.md", "# Plans\n\nUse this area for active implementation plans.\n\n## Naming\n\n- Task directories use kebab-case: `docs/plan/<task-slug>/`.\n- Markdown file names use UPPER_SNAKE_CASE: `README.md`, `RESEARCH_NOTES.md`, `VERIFICATION.md`.\n\n## Structure\n\n- Create one directory per task: `docs/plan/<task-slug>/`.\n- Put the task overview, index, scope, status, and main plan in `docs/plan/<task-slug>/README.md`.\n- Add supporting research, checklists, payload captures, or verification notes as additional UPPER_SNAKE_CASE markdown files in the same task directory.\n- Move completed or superseded task directories to `docs/archive/plan/<task-slug>/`.\n\nThis directory is local-only and ignored by git by default."],
2041
1970
  ["docs/archive/README.md", "# Archive\n\nUse this area for local completed plans, temporary reports, historical notes, payload captures, and investigation notes.\n\n## Naming\n\n- Archived plan task directories preserve their kebab-case task slug.\n- Archived report directories use a focused kebab-case report slug.\n- Markdown file names use UPPER_SNAKE_CASE, including `README.md`.\n\n## Structure\n\n- Move completed plan task directories from `docs/plan/<task-slug>/` to `docs/archive/plan/<task-slug>/`.\n- Put temporary investigations, reports, payload captures, and historical notes under `docs/archive/report/<report-slug>/`.\n- Preserve each archive directory's `README.md` overview/index and supporting UPPER_SNAKE_CASE markdown files.\n- Additional archive categories can be added later as focused kebab-case subdirectories when needed.\n\nThis directory is local-only and ignored by git by default."]
2042
1971
  ];
2043
- if (options.dotdotSetting) {
2044
- files.push(["docs/arch/DOCS_STRUCTURE.md", docsStructureContent()]);
2045
- files.push(["docs/arch/CODE_CONVENTIONS.md", codeConventionsContent()]);
2046
- }
2047
1972
  return files.map(([path, content]) => [path.replace(/^docs(?=\/|$)/, options.documentationRoot), content.replaceAll("docs/", `${options.documentationRoot}/`).replaceAll("`docs`", `\`${options.documentationRoot}\``)]);
2048
1973
  }
2049
1974
  function runInit(argv, usage2) {
@@ -2087,7 +2012,8 @@ function runInit(argv, usage2) {
2087
2012
  ` };
2088
2013
  }
2089
2014
  const actions = [];
2090
- for (const [relativePath, content] of initFiles(options)) writeInitFile(options, relativePath, content, actions);
2015
+ const scaffold = template?.source === "bundled" ? builtInTemplateScaffold(template.name) : [];
2016
+ for (const [relativePath, content] of initFiles(options, scaffold)) writeInitFile(options, relativePath, content, actions);
2091
2017
  if (template?.source === "bundled") {
2092
2018
  for (const item of builtInTemplateScaffold(template.name, { documentationRoot: options.documentationRoot }) ?? []) {
2093
2019
  if (item.type === "directory") ensureInitDirectory(options, item.path, actions);
@@ -2103,7 +2029,7 @@ function runInit(argv, usage2) {
2103
2029
  initError(options, "CLAUDE_SETTINGS_CHANGED", error.message);
2104
2030
  }
2105
2031
  }
2106
- const payload = { ok: true, command: "init", root: options.root, projectName: options.projectName, documentationRoot: options.documentationRoot, dryRun: options.dryRun, dotdotSetting: options.dotdotSetting, template: template ? { name: template.name, source: template.source, selectedBy: template.selectedBy } : null, actions };
2032
+ const payload = { ok: true, command: "init", root: options.root, projectName: options.projectName, documentationRoot: options.documentationRoot, dryRun: options.dryRun, template: template ? { name: template.name, source: template.source, selectedBy: template.selectedBy } : null, actions };
2107
2033
  if (options.json) console.log(JSON.stringify(payload, null, 2));
2108
2034
  else {
2109
2035
  if (template) console.log(`template ${template.source}:${template.name} (${template.selectedBy})`);
@@ -2123,7 +2049,7 @@ function commandUsage(command = "root") {
2123
2049
  dotdotgod validate <root> [--include-local-memory] [--check-index] [--max-lines n] [--max-chars n] [--no-link-check] [--json]`;
2124
2050
  case "init":
2125
2051
  return `Usage:
2126
- dotdotgod init <root> [--project-name NAME] [--template NAME] [--documentation-root PATH] [--dotdot-setting] [--claude-plans] [--dry-run] [--json]
2052
+ dotdotgod init <root> [--project-name NAME] [--template NAME] [--documentation-root PATH] [--claude-plans] [--dry-run] [--json]
2127
2053
 
2128
2054
  Create AGENTS.md, agent entrypoints, docs indexes, a project config from an initialization template, and local memory gitignore entries.
2129
2055
  Use --claude-plans only after opting into project-local native plan storage; existing Claude settings are preserved.`;
@@ -2185,7 +2111,8 @@ Checks or writes generated Markdown traceability link sections from canonical fe
2185
2111
  case "graph":
2186
2112
  return `Usage:
2187
2113
  dotdotgod graph impact <root> --changed <path> [--changed <path> ...] [--compact|--json|--yml|--yaml]
2188
- dotdotgod graph serve <root> --changed <path> [--changed <path> ...] [--host HOST] [--port PORT]
2114
+ dotdotgod graph serve <root> [--host HOST] [--port PORT]
2115
+ dotdotgod graph export <root> [--json]
2189
2116
  dotdotgod graph communities <root> [--json]`;
2190
2117
  case "graph impact":
2191
2118
  return `Usage:
@@ -2194,9 +2121,14 @@ Checks or writes generated Markdown traceability link sections from canonical fe
2194
2121
  Ranks nodes related to one or more changed files. <root> is the project root; repeat --changed with project-relative file paths. Results include a combined ranking and the top five related nodes for each changed file. Use --compact for a short text summary or --yml/--yaml for structured agent-facing output.`;
2195
2122
  case "graph serve":
2196
2123
  return `Usage:
2197
- dotdotgod graph serve <root> --changed <path> [--changed <path> ...] [--host HOST] [--port PORT]
2124
+ dotdotgod graph serve <root> [--host HOST] [--port PORT]
2198
2125
 
2199
- Starts a local-only impact graph explorer. Changed files are rendered as root nodes and are excluded from related-result lists.`;
2126
+ Starts a standalone local traceability graph without Obsidian.`;
2127
+ case "graph export":
2128
+ return `Usage:
2129
+ dotdotgod graph export <root> [--json]
2130
+
2131
+ Write .dotdotgod/traceability.json for the Obsidian plugin.`;
2200
2132
  case "graph communities":
2201
2133
  return `Usage:
2202
2134
  dotdotgod graph communities <root> [--json]`;
@@ -2220,7 +2152,8 @@ Commands:
2220
2152
  status Show local index status.
2221
2153
  traceability links Check or write generated traceability links.
2222
2154
  graph impact Find files related to changed files.
2223
- graph serve Open a local impact graph explorer.
2155
+ graph serve Open a standalone local traceability graph.
2156
+ graph export Export traceability data for Obsidian.
2224
2157
  graph communities Find groups of related project-memory nodes.
2225
2158
 
2226
2159
  Options:
@@ -2731,19 +2664,19 @@ function addConfiguredLocalMemoryCandidates(root, candidates, config, limit = 5e
2731
2664
  let added = 0;
2732
2665
  const addPath = (path) => {
2733
2666
  if (added >= limit || !existsSync7(path)) return;
2734
- let stat;
2667
+ let stat2;
2735
2668
  try {
2736
- stat = statSync2(path);
2669
+ stat2 = statSync2(path);
2737
2670
  } catch {
2738
2671
  return;
2739
2672
  }
2740
2673
  const pathRel = rel(root, path);
2741
- if (stat.isFile()) {
2674
+ if (stat2.isFile()) {
2742
2675
  candidates.add(pathRel);
2743
2676
  added += 1;
2744
2677
  return;
2745
2678
  }
2746
- if (!stat.isDirectory() || isExcludedIndexDir(pathRel)) return;
2679
+ if (!stat2.isDirectory() || isExcludedIndexDir(pathRel)) return;
2747
2680
  for (const entry of readdirSync(path, { withFileTypes: true })) {
2748
2681
  if (added >= limit) return;
2749
2682
  addPath(join6(path, entry.name));
@@ -3226,13 +3159,13 @@ var OUTLINE_SCHEMA = 1;
3226
3159
  var outlinePath = (root) => join11(root, ".dotdotgod", "outlines.json");
3227
3160
  function assertCachePath(root) {
3228
3161
  for (const path of [dirname8(outlinePath(root)), outlinePath(root)]) {
3229
- let stat;
3162
+ let stat2;
3230
3163
  try {
3231
- stat = lstatSync3(path);
3164
+ stat2 = lstatSync3(path);
3232
3165
  } catch (error) {
3233
3166
  if (error.code !== "ENOENT") throw error;
3234
3167
  }
3235
- if (stat?.isSymbolicLink()) throw new Error(`Outline cache must not use symlinks: ${path}`);
3168
+ if (stat2?.isSymbolicLink()) throw new Error(`Outline cache must not use symlinks: ${path}`);
3236
3169
  }
3237
3170
  }
3238
3171
  function readOutlines(root) {
@@ -6100,9 +6033,9 @@ function prepareImpactReferences(index, root, changedPaths) {
6100
6033
  let result;
6101
6034
  try {
6102
6035
  const absolute = resolve15(root, path);
6103
- const stat = statSync8(absolute);
6036
+ const stat2 = statSync8(absolute);
6104
6037
  result = { status: "exists" };
6105
- if (stat.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(readFileSync18(absolute, "utf8"));
6038
+ if (stat2.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(readFileSync18(absolute, "utf8"));
6106
6039
  } catch (error) {
6107
6040
  result = { status: ["ENOENT", "ENOTDIR"].includes(error.code) ? "missing" : "unavailable" };
6108
6041
  }
@@ -6746,126 +6679,57 @@ function runTraceability(argv) {
6746
6679
  process.exit(ok ? 0 : 1);
6747
6680
  }
6748
6681
 
6749
- // packages/cli/src/graph-view/server.mjs
6750
- import { createServer } from "node:http";
6751
- import { readFileSync as readFileSync21 } from "node:fs";
6752
- import { dirname as dirname13, extname as extname10, join as join20 } from "node:path";
6753
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6682
+ // packages/cli/src/commands/graph.mjs
6683
+ import { mkdir, writeFile, rename, rm } from "node:fs/promises";
6684
+ import { join as join21 } from "node:path";
6754
6685
 
6755
- // packages/cli/src/graph-view/payload.mjs
6756
- function edgeKey2(edge) {
6757
- return `${edge.source}\0${edge.target}\0${edge.relation}`;
6758
- }
6759
- function labelForNode(node2) {
6760
- if (node2.type === "contract") return node2.title ?? node2.contractId ?? node2.id;
6761
- return node2.path ?? node2.command ?? node2.name ?? node2.target ?? node2.id;
6762
- }
6763
- function adjacencyFor(nodes, edges) {
6764
- const adjacency = new Map(nodes.map((node2) => [node2.id, /* @__PURE__ */ new Set()]));
6765
- for (const edge of edges) {
6766
- adjacency.get(edge.source)?.add(edge.target);
6767
- adjacency.get(edge.target)?.add(edge.source);
6768
- }
6769
- return adjacency;
6770
- }
6771
- function rootReachability(nodes, edges, seedIds) {
6772
- const adjacency = adjacencyFor(nodes, edges);
6773
- const reachable = /* @__PURE__ */ new Set();
6774
- const depthByNode = /* @__PURE__ */ new Map();
6775
- const nearestRootByNode = /* @__PURE__ */ new Map();
6776
- const queue = [];
6777
- for (const node2 of nodes) {
6778
- if (!seedIds.has(node2.id)) continue;
6779
- reachable.add(node2.id);
6780
- depthByNode.set(node2.id, 0);
6781
- nearestRootByNode.set(node2.id, node2.id);
6782
- queue.push(node2.id);
6783
- }
6784
- for (let cursor = 0; cursor < queue.length; cursor += 1) {
6785
- const current = queue[cursor];
6786
- for (const neighbor of adjacency.get(current) ?? []) {
6787
- if (reachable.has(neighbor)) continue;
6788
- reachable.add(neighbor);
6789
- depthByNode.set(neighbor, (depthByNode.get(current) ?? 0) + 1);
6790
- nearestRootByNode.set(neighbor, nearestRootByNode.get(current));
6791
- queue.push(neighbor);
6792
- }
6793
- }
6794
- return { reachable, depthByNode, nearestRootByNode };
6795
- }
6796
- function buildImpactGraphPayload(index, impact) {
6797
- const graph = impact.structuralGraph ?? index?.graph ?? { nodes: [], edges: [] };
6798
- const config = index?.memoryConfig;
6799
- const seedIds = new Set((impact.changedFiles ?? [impact.changed]).map((path) => `file:${path}`));
6800
- const impactItems = new Map((impact.related ?? []).map((item) => [item.id, item]));
6801
- const isAllowedNode = (node2) => {
6802
- if (!node2) return false;
6803
- if (node2.path && resolveMemoryArea(node2.path, config)?.scope === "local") return false;
6804
- return true;
6805
- };
6806
- const sourceNodes = (graph.nodes ?? []).filter(isAllowedNode);
6807
- const indexedNodes = new Map(sourceNodes.map((node2) => [node2.id, node2]));
6808
- const seenEdges = /* @__PURE__ */ new Set();
6686
+ // packages/cli/src/graph-view/traceability.mjs
6687
+ import { resolve as resolve19 } from "node:path";
6688
+ import { stat } from "node:fs/promises";
6689
+ async function exportTraceability(root) {
6690
+ const config = readMemoryConfig(root);
6691
+ if (config.errors?.length) throw new Error(JSON.stringify(config.errors));
6692
+ const { index } = readFreshIndex(root);
6693
+ const nodes = new Map(index.graph.nodes.map((n) => [n.id, n]));
6694
+ const colors = ["#b9a3ff", "#70c9a2", "#72b9e6", "#d6b879", "#e393a9", "#78c9cd"];
6695
+ const types = config.traceability.keys.map((k, i) => ({ ...k, color: colors[i % colors.length] }));
6696
+ const typesByRelation = new Map(types.map((t) => [t.relation, t]));
6809
6697
  const edges = [];
6810
- for (const edge of graph.edges ?? []) {
6811
- if (!indexedNodes.has(edge.source) || !indexedNodes.has(edge.target)) continue;
6812
- const key = edgeKey2(edge);
6813
- if (seenEdges.has(key)) continue;
6814
- seenEdges.add(key);
6815
- edges.push({ source: edge.source, target: edge.target, relation: edge.relation, confidence: edge.confidence, weight: edge.weight ?? edge.relationWeight, score: edge.score, traceabilityKey: edge.traceabilityKey });
6816
- }
6817
- const { reachable, depthByNode, nearestRootByNode } = rootReachability(sourceNodes, edges, seedIds);
6818
- const retainedNodes = sourceNodes.filter((node2) => reachable.has(node2.id));
6819
- const retainedEdges = edges.filter((edge) => reachable.has(edge.source) && reachable.has(edge.target));
6820
- const nodes = retainedNodes.map((source) => {
6821
- const item = impactItems.get(source.id);
6822
- const seed = seedIds.has(source.id);
6823
- return {
6824
- id: source.id,
6825
- type: source.type ?? item?.type ?? "file",
6826
- path: source.path ?? item?.path,
6827
- label: labelForNode({ ...source, ...item }),
6828
- seed,
6829
- ranked: Boolean(item),
6830
- depth: depthByNode.get(source.id) ?? 0,
6831
- nearestRoot: nearestRootByNode.get(source.id),
6832
- component: 0,
6833
- seededComponent: true,
6834
- impactScore: seed ? 100 : item?.impactScore ?? 0,
6835
- reasons: seed ? ["changed-file"] : item?.reasons ?? [],
6836
- scoreBreakdown: seed ? { seed: 100 } : item?.scoreBreakdown,
6837
- retrieval: item?.retrieval ?? source.retrieval,
6838
- contractId: item?.contractId ?? source.contractId,
6839
- title: item?.title ?? source.title,
6840
- sections: item?.sections ?? source.sections,
6841
- vectorEvidence: item?.vectorEvidence
6842
- };
6843
- });
6698
+ for (const edge of index.graph.edges) {
6699
+ const type = typesByRelation.get(edge.relation);
6700
+ const source = nodes.get(edge.source)?.path;
6701
+ const target = nodes.get(edge.target)?.path;
6702
+ if (!type || !source || !target || source === target) continue;
6703
+ if ([source, target].some((p) => resolveMemoryArea(p, config)?.scope === "local")) continue;
6704
+ if (!(await Promise.all([source, target].map(async (p) => {
6705
+ const absolute = resolve19(root, p);
6706
+ if (!absolute.startsWith(resolve19(root) + "/")) return false;
6707
+ try {
6708
+ return (await stat(absolute)).isFile();
6709
+ } catch {
6710
+ return false;
6711
+ }
6712
+ }))).every(Boolean)) continue;
6713
+ edges.push({ source, target, relation: type.relation, weight: type.weight, color: type.color });
6714
+ }
6844
6715
  return {
6845
- changed: impact.changed,
6846
- changedFiles: impact.changedFiles ?? [impact.changed],
6847
- ranking: impact.ranking,
6848
- semantic: impact.semantic,
6849
- complete: true,
6850
- nodes,
6851
- edges: retainedEdges,
6852
- components: retainedNodes.length > 0 ? [{ id: 0, size: retainedNodes.length, seeded: true }] : [],
6853
- diagnostics: {
6854
- rootNodes: retainedNodes.filter((node2) => seedIds.has(node2.id)).length,
6855
- connectedNodes: retainedNodes.length,
6856
- disconnectedNodesOmitted: sourceNodes.length - retainedNodes.length,
6857
- maximumDepth: Math.max(0, ...depthByNode.values())
6858
- },
6859
- omittedRelated: impact.omittedRelated ?? 0
6716
+ version: 1,
6717
+ projectRoot: resolve19(root),
6718
+ config: memoryConfigSummary(config),
6719
+ types,
6720
+ edges: [...new Map(edges.map((e) => [`${e.source}\0${e.target}\0${e.relation}`, e])).values()]
6860
6721
  };
6861
6722
  }
6862
6723
 
6863
6724
  // packages/cli/src/graph-view/server.mjs
6725
+ import { createServer } from "node:http";
6726
+ import { readFileSync as readFileSync21 } from "node:fs";
6727
+ import { dirname as dirname13, extname as extname10, join as join20, resolve as resolve20 } from "node:path";
6728
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
6729
+ var import_node_style = __toESM(require_node_style(), 1);
6864
6730
  var ASSET_ROOT = join20(dirname13(fileURLToPath2(import.meta.url)), "assets");
6865
- var CONTENT_TYPES = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".map": "application/json; charset=utf-8" };
6731
+ var CONTENT_TYPES = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".cjs": "text/javascript; charset=utf-8", ".map": "application/json; charset=utf-8" };
6866
6732
  var VENDOR_FILES = {
6867
- "/vendor/sigma.js": new URL("../../node_modules/sigma/dist/sigma.min.js", import.meta.url),
6868
- "/vendor/graphology.js": new URL("../../node_modules/graphology/dist/graphology.umd.min.js", import.meta.url),
6869
6733
  "/vendor/d3.js": new URL("../../node_modules/d3/dist/d3.min.js", import.meta.url)
6870
6734
  };
6871
6735
  function json(response, status, value) {
@@ -6882,49 +6746,42 @@ function asset(response, file) {
6882
6746
  response.end("Not found");
6883
6747
  }
6884
6748
  }
6885
- async function buildServedImpact(root, changedPaths) {
6886
- const normalized = [...new Set(changedPaths.map((path) => canonicalizeChangedPath(root, path)).filter(Boolean))];
6887
- const { status, index, metadata } = readFreshIndex(root);
6888
- const overlay = await buildVectorImpactOverlay(root, index, normalized);
6889
- const impact = buildImpactReport(index, normalized, { root, overlay, verboseSemantic: false, related: 40 });
6890
- const graph = buildImpactGraphPayload(index, impact);
6891
- const graphIds = new Set(graph.nodes.map((node2) => node2.id));
6892
- const explorerImpact = {
6893
- ...impact,
6894
- related: impact.related.filter((item) => graphIds.has(item.id))
6895
- };
6896
- return { ok: status.ok, status, metadata, impact: explorerImpact, graph };
6897
- }
6898
6749
  async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port = 0 }) {
6899
- let payload = await buildServedImpact(root, changed);
6750
+ root = resolve20(root);
6751
+ if (!["127.0.0.1", "localhost", "::1"].includes(host)) throw new Error("Graph UI is local-only; use a loopback host.");
6752
+ const build = async () => {
6753
+ const data = await exportTraceability(root);
6754
+ const paths = [...new Set(data.edges.flatMap((e) => [e.source, e.target]))];
6755
+ return { ...data, projectRoot: void 0, nodes: paths.map((path) => ({ id: path, ...import_node_style.default.describeNode(path, data.config.documentation.root) })) };
6756
+ };
6757
+ let payload = await build();
6900
6758
  const server = createServer(async (request, response) => {
6901
6759
  const url = new URL(request.url ?? "/", `http://${request.headers.host ?? host}`);
6902
- if (url.pathname === "/api/impact") return json(response, 200, payload);
6903
- if (url.pathname === "/api/reroot") {
6904
- const paths = url.searchParams.getAll("changed");
6905
- if (paths.length === 0) return json(response, 400, { ok: false, error: { code: "MISSING_CHANGED", message: "At least one changed path is required." } });
6760
+ if (!["GET", "HEAD"].includes(request.method)) return json(response, 405, { error: "Method not allowed" });
6761
+ if (url.pathname === "/api/graph") {
6906
6762
  try {
6907
- payload = await buildServedImpact(root, paths);
6763
+ payload = await build();
6908
6764
  return json(response, 200, payload);
6909
6765
  } catch (error) {
6910
6766
  return json(response, 500, { ok: false, error: { code: "GRAPH_SERVE_FAILED", message: error instanceof Error ? error.message : String(error) } });
6911
6767
  }
6912
6768
  }
6913
6769
  if (VENDOR_FILES[url.pathname]) return asset(response, fileURLToPath2(VENDOR_FILES[url.pathname]));
6770
+ if (url.pathname === "/policy.js") return asset(response, join20(ASSET_ROOT, "../policy.cjs"));
6914
6771
  const relative8 = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
6915
- if (!["index.html", "app.css", "app.js"].includes(relative8)) {
6772
+ if (!["index.html", "app.css", "app.js", "file-list.js", "graph-client.js", "layout.js"].includes(relative8)) {
6916
6773
  response.writeHead(404);
6917
6774
  return response.end("Not found");
6918
6775
  }
6919
6776
  return asset(response, join20(ASSET_ROOT, relative8));
6920
6777
  });
6921
- await new Promise((resolve19, reject) => {
6778
+ await new Promise((resolve21, reject) => {
6922
6779
  server.once("error", reject);
6923
- server.listen(port, host, resolve19);
6780
+ server.listen(port, host, resolve21);
6924
6781
  });
6925
6782
  const address = server.address();
6926
6783
  const actualPort = typeof address === "object" && address ? address.port : port;
6927
- return { server, url: `http://${host}:${actualPort}`, payload };
6784
+ return { server, url: `http://${host.includes(":") ? `[${host}]` : host}:${actualPort}`, payload };
6928
6785
  }
6929
6786
 
6930
6787
  // packages/cli/src/impact/format.mjs
@@ -7080,9 +6937,25 @@ async function runGraph(argv) {
7080
6937
  const sub = argv[0];
7081
6938
  const isImpact = sub === "impact";
7082
6939
  const isServe = sub === "serve";
7083
- if (!["impact", "communities", "serve"].includes(sub)) usage(sub ? `Unknown graph command: ${sub}` : "Missing graph command.", "graph");
6940
+ if (!["impact", "communities", "serve", "export"].includes(sub)) usage(sub ? `Unknown graph command: ${sub}` : "Missing graph command.", "graph");
7084
6941
  const options = parseGraphOptions(argv.slice(1));
7085
- if (isServe && options.changed.length === 0) usage("Missing required option: --changed <path>.", "graph serve");
6942
+ if (sub === "export") {
6943
+ const graph = await exportTraceability(options.root);
6944
+ delete graph.projectRoot;
6945
+ const directory = join21(options.root, ".dotdotgod");
6946
+ await mkdir(directory, { recursive: true });
6947
+ const path = join21(directory, "traceability.json");
6948
+ const temporary = `${path}.${process.pid}.tmp`;
6949
+ try {
6950
+ await writeFile(temporary, JSON.stringify(graph, null, 2));
6951
+ await rename(temporary, path);
6952
+ } finally {
6953
+ await rm(temporary, { force: true });
6954
+ }
6955
+ console.log(options.json ? JSON.stringify({ ok: true, path, edges: graph.edges.length }) : `graph export: ${path} (${graph.edges.length} relations)`);
6956
+ return;
6957
+ }
6958
+ if (isServe && options.changed.length > 0) usage("graph serve now shows project traceability. Use graph impact for --changed analysis.", "graph serve");
7086
6959
  if (isServe && !Number.isInteger(options.port)) usage("Invalid --port. Use an integer from 0 through 65535.", "graph serve");
7087
6960
  if (isServe) {
7088
6961
  options.changed = [...new Set(options.changed.map((path) => canonicalizeChangedPath(options.root, path)).filter(Boolean))];
package/mcp/server.mjs CHANGED
@@ -23310,7 +23310,6 @@ async function projectInitialize(input) {
23310
23310
  if (dryRun) args.push("--dry-run");
23311
23311
  if (input.projectName) args.push("--project-name", input.projectName);
23312
23312
  if (input.template) args.push("--template", input.template);
23313
- if (input.dotdotSetting) args.push("--dotdot-setting");
23314
23313
  return await cliJson(args, root2);
23315
23314
  }
23316
23315
 
@@ -23545,7 +23544,7 @@ function getJobs() {
23545
23544
  jobRunner ??= new IngestionJobRunner(getStore());
23546
23545
  return jobRunner;
23547
23546
  }
23548
- var server = new McpServer({ name: "dotdotgod-context", version: "0.6.7" });
23547
+ var server = new McpServer({ name: "dotdotgod-context", version: "0.7.0" });
23549
23548
  var scopeSchema = external_exports.enum(["transient", "session", "project"]).optional();
23550
23549
  var outputModeSchema = external_exports.enum(["auto", "direct", "indexed", "discard"]).optional();
23551
23550
  var commandSchema = {
@@ -23702,8 +23701,7 @@ register("project_initialize", "Initialize project memory; dry-run is default an
23702
23701
  dryRun: external_exports.boolean().optional(),
23703
23702
  confirmWrite: external_exports.boolean().optional(),
23704
23703
  projectName: external_exports.string().optional(),
23705
- template: external_exports.string().optional(),
23706
- dotdotSetting: external_exports.boolean().optional()
23704
+ template: external_exports.string().optional()
23707
23705
  }, (input) => projectInitialize(projectInput(input)), { destructiveHint: true });
23708
23706
  async function startServer() {
23709
23707
  await server.connect(new StdioServerTransport());
package/mcp/tools.json CHANGED
@@ -814,9 +814,6 @@
814
814
  "template": {
815
815
  "type": "string"
816
816
  },
817
- "dotdotSetting": {
818
- "type": "boolean"
819
- },
820
817
  "contextSessionId": {
821
818
  "type": "string",
822
819
  "description": "Host session identity; adapters supply this for shared MCP connections."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/hermes",
3
- "version": "0.6.7",
3
+ "version": "0.7.0",
4
4
  "description": "Hermes CLI and messaging-gateway adapter without Plan Mode.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -13,7 +13,7 @@
13
13
  "devDependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.25.2",
15
15
  "esbuild": "0.27.3",
16
- "@dotdotgod/context": "0.6.7"
16
+ "@dotdotgod/context": "0.7.0"
17
17
  },
18
18
  "files": [
19
19
  "plugin.yaml",
package/plugin.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: dotdotgod
2
- version: 0.6.7
2
+ version: 0.7.0
3
3
  description: Project memory, isolated context tools and impact checks for Hermes CLI and messaging gateway; no Plan Mode.
4
4
  author: dotdotgod
5
5
  license: Elastic-2.0
@@ -6,15 +6,13 @@ TEMPLATES_DIR="$SCRIPT_DIR/../templates"
6
6
 
7
7
  usage() {
8
8
  cat <<'EOF'
9
- Usage: init_project.sh <project-root> [--project-name NAME] [--template NAME] [--documentation-root PATH] [--dotdot-setting] [--dry-run]
9
+ Usage: init_project.sh <project-root> [--project-name NAME] [--template NAME] [--documentation-root PATH] [--dry-run]
10
10
 
11
11
  Initializes:
12
12
  AGENTS.md, CLAUDE.md, CODEX.md
13
13
  dotdotgod.config.json
14
14
  docs/README.md
15
- docs/spec/README.md
16
- docs/test/README.md
17
- docs/arch/README.md
15
+ Template-specific domain indexes (spec/arch/test only for software and research)
18
16
  docs/plan/README.md
19
17
  docs/archive/README.md
20
18
  .gitignore entries for docs/plan, docs/archive, and .dotdotgod
@@ -24,7 +22,6 @@ EOF
24
22
  PROJECT_ROOT=""
25
23
  PROJECT_NAME=""
26
24
  DRY_RUN=0
27
- DOTDOT_SETTING=0
28
25
  TEMPLATE_NAME="software"
29
26
  DOCUMENTATION_ROOT="docs"
30
27
 
@@ -51,10 +48,6 @@ while [ "$#" -gt 0 ]; do
51
48
  DOCUMENTATION_ROOT=$2
52
49
  shift 2
53
50
  ;;
54
- --dotdot-setting)
55
- DOTDOT_SETTING=1
56
- shift
57
- ;;
58
51
  --dry-run)
59
52
  DRY_RUN=1
60
53
  shift
@@ -195,21 +188,11 @@ if [ "$DRY_RUN" -ne 1 ]; then
195
188
  mkdir -p "$PROJECT_ROOT"
196
189
  fi
197
190
 
198
- DOTDOT_AGENT_RULE=""
199
- if [ "$DOTDOT_SETTING" -eq 1 ]; then
200
- DOTDOT_AGENT_RULE='
201
- - Follow the project documentation structure in `$DOCUMENTATION_ROOT/arch/DOCS_STRUCTURE.md` and code conventions in `$DOCUMENTATION_ROOT/arch/CODE_CONVENTIONS.md`.'
202
- fi
203
-
204
- ARCH_README_EXTRA=""
205
- if [ "$DOTDOT_SETTING" -eq 1 ]; then
206
- ARCH_README_EXTRA='
207
-
208
- ## Index
209
-
210
- - `DOCS_STRUCTURE.md`: documentation layout, naming, README index, spec current-state writing contract, and domain directory promotion rules.
211
- - `CODE_CONVENTIONS.md`: dotdot code conventions, including abstraction boundaries, source file size guidance, impact hotspot handling, and extraction/testability rules. If conventions grow across multiple topics, promote them to `conventions/README.md` with supporting UPPER_SNAKE_CASE files.'
191
+ DOMAIN_MAP=""
192
+ if [ ! -e "$CONFIG_PATH" ]; then
193
+ DOMAIN_MAP=$(cat "$TEMPLATES_DIR/$TEMPLATE_NAME.map.md")
212
194
  fi
195
+ DOMAIN_MAP=$(printf '%s' "$DOMAIN_MAP" | sed "s#\`docs/#\`$DOCUMENTATION_ROOT/#g")
213
196
 
214
197
  write_file "$PROJECT_ROOT/AGENTS.md" "# AGENTS.md
215
198
 
@@ -228,7 +211,7 @@ Canonical instructions for AI coding agents working in this repository.
228
211
  - Preserve user edits and unrelated dirty worktree changes.
229
212
  - Prefer existing local patterns over introducing new abstractions.
230
213
  - Update docs when behavior, architecture, or test strategy changes.
231
- - When using the dotdotgod CLI, run \`dotdotgod validate\` after docs changes and follow its traceability guidance for behavior specs.$DOTDOT_AGENT_RULE
214
+ - When using the dotdotgod CLI, run \`dotdotgod validate\` after docs changes and follow its traceability guidance.
232
215
 
233
216
  ## dotdotgod
234
217
 
@@ -253,15 +236,12 @@ TODO
253
236
 
254
237
  ## Documentation Map
255
238
 
256
- - \`$DOCUMENTATION_ROOT/spec/\`: product behavior, API contracts, user-facing requirements.
257
- - \`$DOCUMENTATION_ROOT/test/\`: test strategy, regression cases, manual verification notes.
258
- - \`$DOCUMENTATION_ROOT/arch/\`: architecture decisions, code conventions, module boundaries, data flow, infrastructure/runtime dependencies, integration boundaries, and migration design.
239
+ $DOMAIN_MAP
259
240
  - \`$DOCUMENTATION_ROOT/\`: all directories use kebab-case; all markdown file names use UPPER_SNAKE_CASE, including \`README.md\`.
260
241
  - \`$DOCUMENTATION_ROOT/\`: prefer keeping individual markdown files under 200 lines and under 10,000 characters; split larger docs into focused UPPER_SNAKE_CASE files and keep \`README.md\` as the index/overview.
261
242
  - \`$DOCUMENTATION_ROOT/\`: when adding, renaming, splitting, moving, or archiving docs, update the nearest relevant \`README.md\` index/table of contents in the same change.
262
243
  - \`$DOCUMENTATION_ROOT/\`: each docs subdirectory \`README.md\` acts as the local table of contents; list important files, task directories, status, and a one-line purpose for each entry.
263
244
  - \`$DOCUMENTATION_ROOT/\`: start small with a single focused markdown file; when one domain grows into multiple docs, promote it to \`$DOCUMENTATION_ROOT/<area>/<domain>/README.md\` plus related UPPER_SNAKE_CASE files in that directory.
264
- - \`$DOCUMENTATION_ROOT/arch/\`: code conventions may start as \`CODE_CONVENTIONS.md\`; when they grow across multiple topics, use \`$DOCUMENTATION_ROOT/arch/conventions/README.md\` as the index with supporting UPPER_SNAKE_CASE files.
265
245
  - \`$DOCUMENTATION_ROOT/plan/\`: local active implementation plans. Create one kebab-case directory per task (\`$DOCUMENTATION_ROOT/plan/<task-slug>/\`), keep the task overview/index in that directory's \`README.md\`, and add supporting UPPER_SNAKE_CASE plan files alongside it. Ignored by git by default.
266
246
  - \`$DOCUMENTATION_ROOT/archive/\`: local completed plans, temporary reports, historical notes, payload captures. Move completed plan task directories to \`$DOCUMENTATION_ROOT/archive/plan/<task-slug>/\`; put temporary reports and investigations under \`$DOCUMENTATION_ROOT/archive/report/<report-slug>/\`. Ignored by git by default.
267
247
 
@@ -298,111 +278,10 @@ This directory keeps project knowledge close to the code.
298
278
 
299
279
  ## Map
300
280
 
301
- - \`spec/\`: product behavior, API contracts, user-facing requirements.
302
- - \`test/\`: test strategy, regression cases, manual verification notes.
303
- - \`arch/\`: architecture decisions, code conventions, module boundaries, data flow, infrastructure/runtime dependencies, integration boundaries, and migration design.
281
+ $(printf '%s' "$DOMAIN_MAP" | sed "s#\`$DOCUMENTATION_ROOT/#\`#g")
304
282
  - \`plan/\`: local active implementation plans. Create one kebab-case directory per task (\`plan/<task-slug>/\`), keep the task overview/index in that directory's \`README.md\`, and add supporting UPPER_SNAKE_CASE plan files alongside it. Ignored by git by default.
305
283
  - \`archive/\`: local completed plans, temporary reports, historical notes, payload captures. Move completed plan task directories to \`archive/plan/<task-slug>/\`; put temporary reports and investigations under \`archive/report/<report-slug>/\`. Ignored by git by default."
306
284
 
307
- write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/spec/README.md" "# Specs
308
-
309
- Use this area for behavior specs, API contracts, and product requirements.
310
-
311
- For projects using the dotdotgod CLI, behavior specs may be required by \`dotdotgod validate\` to include fenced \`json dotdotgod\` traceability blocks as the final section. The CLI owns the schema and prints property-level repair guidance when validation fails."
312
-
313
- write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/test/README.md" "# Tests
314
-
315
- Use this area for test strategy, coverage notes, regression cases, and manual verification records."
316
-
317
- write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/arch/README.md" "# Architecture
318
-
319
- Use this area for architecture decisions, code conventions, module boundaries, data flow notes, infrastructure/runtime dependencies, integration boundaries, and migration design.$ARCH_README_EXTRA"
320
-
321
- if [ "$DOTDOT_SETTING" -eq 1 ]; then
322
- write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/arch/DOCS_STRUCTURE.md" "# Docs Structure
323
-
324
- Long-term documentation structure for this project.
325
-
326
- ## Top-Level Areas
327
-
328
- - \`$DOCUMENTATION_ROOT/spec/\`: product behavior, API contracts, user-facing requirements, and feature contracts.
329
- - \`$DOCUMENTATION_ROOT/test/\`: test strategy, coverage notes, regression cases, and manual verification records.
330
- - \`$DOCUMENTATION_ROOT/arch/\`: architecture decisions, code conventions, module boundaries, data flow, infrastructure/runtime dependencies, integration boundaries, and migration design.
331
- - \`$DOCUMENTATION_ROOT/plan/\`: local active implementation plans.
332
- - \`$DOCUMENTATION_ROOT/archive/\`: local completed plans, historical notes, payload captures, and investigation notes.
333
-
334
- ## Naming
335
-
336
- - Directories under \`$DOCUMENTATION_ROOT/\` use kebab-case.
337
- - Markdown files under \`$DOCUMENTATION_ROOT/\` use UPPER_SNAKE_CASE.
338
- - \`README.md\` is the only mixed-case markdown filename exception and is required for index/overview files.
339
-
340
- ## File Size Guideline
341
-
342
- Prefer keeping individual markdown files under 200 lines and 10,000 characters. When either guideline is exceeded, split the document into focused UPPER_SNAKE_CASE files and keep \`README.md\` as the index/overview. Configured validation exceptions should stay narrow and intentional.
343
-
344
- ## README Indexes
345
-
346
- Each docs subdirectory \`README.md\` acts as the local table of contents. It should list important files, task directories, status, and a one-line purpose for each entry.
347
-
348
- When adding, renaming, splitting, moving, or archiving docs, update the nearest relevant \`README.md\` in the same change.
349
-
350
- ## Domain Directory Promotion
351
-
352
- Start small with one focused markdown file. When one domain grows into multiple docs, promote it to \`$DOCUMENTATION_ROOT/<area>/<domain>/README.md\` and place related UPPER_SNAKE_CASE markdown files in that directory.
353
-
354
- ## Spec Writing Contract
355
-
356
- Behavior specs describe the current product contract: supported commands, API shapes, user-visible behavior, defaults, constraints, and validation outcomes.
357
-
358
- Specs should not describe how behavior changed over time. Rewrite historical-change wording into direct current-state rules. Historical context, migration rationale, future extension ideas, and completed-plan notes belong in \`$DOCUMENTATION_ROOT/arch/\`, \`$DOCUMENTATION_ROOT/test/\`, \`$DOCUMENTATION_ROOT/archive/\`, or active \`$DOCUMENTATION_ROOT/plan/\` files rather than behavior specs. If compatibility behavior is still user-visible, keep it in the spec but phrase it as a current supported or unsupported rule.
359
-
360
- Config/action terms such as \`remove\`, \`exclude\`, \`fallback\`, and \`replacement semantics\` are allowed when they name current behavior precisely.
361
-
362
- ## Traceability Blocks
363
-
364
- Behavior specs may include fenced \`json dotdotgod\` traceability blocks as the final section to connect specs to source, tests, related docs, and verification commands. The dotdotgod CLI owns the schema and validation behavior.
365
-
366
- ## Plan and Archive Directories
367
-
368
- Active task plans use \`$DOCUMENTATION_ROOT/plan/<task-slug>/README.md\`. Completed or superseded plan task directories move to \`$DOCUMENTATION_ROOT/archive/plan/<task-slug>/\`. Temporary investigations, reports, payload captures, and historical notes move to \`$DOCUMENTATION_ROOT/archive/report/<report-slug>/\`.
369
-
370
- \`$DOCUMENTATION_ROOT/plan\` and \`$DOCUMENTATION_ROOT/archive\` are ignored by git by default.
371
- "
372
-
373
- write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/arch/CODE_CONVENTIONS.md" "# Code Conventions
374
-
375
- Dotdot code conventions for keeping implementation simple and maintainable.
376
-
377
- ## Abstraction Boundaries
378
-
379
- - Do not introduce unnecessary abstractions.
380
- - Do not abstract code that is not reused.
381
- - Do not abstract reused code when the reused behavior is likely to split into separate features or flows later.
382
- - Prefer local, explicit code until a stable reuse pattern appears.
383
-
384
- ## Source File Size
385
-
386
- - Keep source files small enough to read in one focused pass by humans and coding agents.
387
- - If code grows beyond 150 lines, consider splitting or extracting focused units even when it is not reused.
388
- - Review files approaching 250 lines for focused extraction by responsibility.
389
- - Split by behavior or responsibility, not by arbitrary layers.
390
-
391
- ## Dotdotgod Impact Hotspots
392
-
393
- - Treat repeated \`dotdotgod graph impact\` results that collapse onto one large file as a design signal, not as normal precision.
394
- - Dotdotgod impact reveals mixed-responsibility hotspots; it does not replace focused module boundaries.
395
- - When unrelated changes keep pointing to the same source file, split the file by behavior so impact results, tests, and docs can map to narrower responsibilities.
396
-
397
- ## Extraction and Testability
398
-
399
- - Prefer extracting pure helpers when behavior can be tested without runtime dependencies.
400
- - Keep runtime integration explicit and local until reuse is stable.
401
- - Put testable logic in focused modules before adding broad framework abstractions.
402
- - Preserve plain-text readability: avoid dense clever code, hidden control flow, and large mixed-responsibility files.
403
- "
404
- fi
405
-
406
285
  write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/plan/README.md" "# Plans
407
286
 
408
287
  Use this area for active implementation plans.
@@ -441,6 +320,19 @@ Use this area for local completed plans, temporary reports, historical notes, pa
441
320
  This directory is local-only and ignored by git by default."
442
321
 
443
322
  if [ ! -e "$CONFIG_PATH" ]; then
323
+ case "$TEMPLATE_NAME" in
324
+ software|research)
325
+ write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/spec/README.md" "# Specs
326
+
327
+ Use this area for behavior specs, API contracts, and requirements. Follow \`dotdotgod validate\` traceability guidance for applicable documents."
328
+ write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/arch/README.md" "# Architecture
329
+
330
+ Use this area for architecture decisions, design rationale, conventions, and integration boundaries."
331
+ write_file "$PROJECT_ROOT/$DOCUMENTATION_ROOT/test/README.md" "# Verification
332
+
333
+ Use this area for test strategy, evaluation methods, regression cases, and verification records."
334
+ ;;
335
+ esac
444
336
  case "$TEMPLATE_NAME" in
445
337
  software)
446
338
  ;;
@@ -0,0 +1,3 @@
1
+ - `docs/case/`: Use this area for canonical case facts, questions, and decisions.
2
+ - `docs/evidence/`: Use this area for factual, legal, and other supporting evidence.
3
+ - `docs/outputs/`: Use this area for maintained case outputs.
@@ -0,0 +1,4 @@
1
+ - `docs/policy/`: Use this area for integrated policy proposals and outputs.
2
+ - `docs/sections/`: Use this area for source policy sections.
3
+ - `docs/evidence/`: Use this area for evidence supporting policy proposals.
4
+ - `docs/outputs/`: Use this area for maintained policy submission outputs.
@@ -0,0 +1,4 @@
1
+ - `docs/strategy/`: Use this area for portfolio strategy and risk rules.
2
+ - `docs/positions/`: Use this area for position theses and investment conclusions.
3
+ - `docs/journal/`: Use this area for dated investment decisions and reviews.
4
+ - `docs/report/`: Use this area for maintained market research and analysis.
@@ -0,0 +1,4 @@
1
+ - `docs/outline/`: Use this area for publication structure and outlines.
2
+ - `docs/chapters/`: Use this area for chapter plans and direction.
3
+ - `docs/claims/`: Use this area for maintained claims and their support.
4
+ - `docs/research/`: Use this area for research sources and supporting notes.
@@ -0,0 +1,6 @@
1
+ - `docs/spec/`: Use this area for behavior specs, API contracts, and requirements. Follow `dotdotgod validate` traceability guidance for applicable documents.
2
+ - `docs/arch/`: Use this area for architecture decisions, design rationale, conventions, and integration boundaries.
3
+ - `docs/test/`: Use this area for test strategy, evaluation methods, regression cases, and verification records.
4
+ - `docs/research/`: Use this area for research notes, sources, and findings.
5
+ - `docs/record/`: Use this area for dated measurements, experiments, and execution records.
6
+ - `docs/report/`: Use this area for research diagnoses, analyses, results, and performance reports.
@@ -0,0 +1,3 @@
1
+ - `docs/spec/`: Use this area for behavior specs, API contracts, and requirements. Follow `dotdotgod validate` traceability guidance for applicable documents.
2
+ - `docs/arch/`: Use this area for architecture decisions, design rationale, conventions, and integration boundaries.
3
+ - `docs/test/`: Use this area for test strategy, evaluation methods, regression cases, and verification records.