recall-os 0.2.1 → 0.3.1

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 (33) hide show
  1. package/README.md +41 -32
  2. package/dist/cli.js +451 -81
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +451 -81
  5. package/dist/index.js.map +1 -1
  6. package/examples/generated-flutter/AGENTS.md +28 -0
  7. package/examples/generated-flutter/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  8. package/examples/generated-flutter/docs/ai/RECALL_COMMANDS.md +13 -1
  9. package/examples/generated-generic/AGENTS.md +28 -0
  10. package/examples/generated-generic/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  11. package/examples/generated-generic/docs/ai/RECALL_COMMANDS.md +13 -1
  12. package/examples/generated-ios-swift/AGENTS.md +28 -0
  13. package/examples/generated-ios-swift/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  14. package/examples/generated-ios-swift/docs/ai/RECALL_COMMANDS.md +13 -1
  15. package/examples/generated-kotlin-android/AGENTS.md +28 -0
  16. package/examples/generated-kotlin-android/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  17. package/examples/generated-kotlin-android/docs/ai/RECALL_COMMANDS.md +13 -1
  18. package/examples/generated-laravel-api/AGENTS.md +28 -0
  19. package/examples/generated-laravel-api/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  20. package/examples/generated-laravel-api/docs/ai/RECALL_COMMANDS.md +13 -1
  21. package/examples/generated-laravel-react/AGENTS.md +28 -0
  22. package/examples/generated-laravel-react/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  23. package/examples/generated-laravel-react/docs/ai/RECALL_COMMANDS.md +13 -1
  24. package/examples/generated-laravel-vue/AGENTS.md +28 -0
  25. package/examples/generated-laravel-vue/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  26. package/examples/generated-laravel-vue/docs/ai/RECALL_COMMANDS.md +13 -1
  27. package/examples/generated-nextjs/AGENTS.md +28 -0
  28. package/examples/generated-nextjs/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  29. package/examples/generated-nextjs/docs/ai/RECALL_COMMANDS.md +13 -1
  30. package/examples/generated-python-fastapi/AGENTS.md +28 -0
  31. package/examples/generated-python-fastapi/docs/60-engineering/AI_AGENT_RULES.md +9 -0
  32. package/examples/generated-python-fastapi/docs/ai/RECALL_COMMANDS.md +13 -1
  33. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -132,8 +132,8 @@ function parseConfig(value) {
132
132
  if (!result.success) {
133
133
  throw new ConfigValidationError(
134
134
  result.error.issues.map((issue) => {
135
- const path17 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
136
- return `${path17}${issue.message}`;
135
+ const path19 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
136
+ return `${path19}${issue.message}`;
137
137
  })
138
138
  );
139
139
  }
@@ -798,6 +798,54 @@ function generateAdrFile(options) {
798
798
  }
799
799
  ];
800
800
  }
801
+ var supersedingAdrTemplate = `# {{adrId}}: {{title}}
802
+
803
+ ## Status
804
+
805
+ Accepted
806
+
807
+ ## Supersedes
808
+
809
+ - {{supersedesRef}}
810
+
811
+ ## Context
812
+
813
+ What changed, and why the previous decision no longer holds?
814
+
815
+ ## Decision
816
+
817
+ What is the new decision?
818
+
819
+ ## Alternatives Considered
820
+
821
+ What other options were considered?
822
+
823
+ ## Consequences
824
+
825
+ What improves, what worsens, and what risks remain?
826
+
827
+ ## Related Documents
828
+
829
+ - PRD:
830
+ - Architecture:
831
+ - Security:
832
+ - Feature:
833
+ `;
834
+ function generateSupersedingAdr(options) {
835
+ const slug = slugify(options.title);
836
+ const title = titleizeAdrTitle(options.title);
837
+ const context = createTemplateContext({
838
+ adrId: options.adrId,
839
+ slug,
840
+ title,
841
+ supersedesRef: options.supersedesRef
842
+ });
843
+ return {
844
+ path: path4.posix.join(options.adrDir, `${options.adrId}-${slug}.md`),
845
+ content: renderTemplate(supersedingAdrTemplate, context),
846
+ slug
847
+ };
848
+ }
801
849
  function titleizeAdrTitle(title) {
802
850
  return title.trim().replace(/[-_]+/gu, " ").replace(/\s+/gu, " ").replace(/\b\w/gu, (character) => character.toUpperCase());
803
851
  }
@@ -888,8 +936,148 @@ async function loadRequiredConfig2(rootDir) {
888
936
  }
889
937
  }
890
938
 
891
- // src/core/generator/generate-feature.ts
939
+ // src/commands/adr/supersede.ts
940
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
892
941
  import path5 from "path";
942
+ var AdrSupersedeError = class extends Error {
943
+ code;
944
+ details;
945
+ constructor(code, message, details = []) {
946
+ super(message);
947
+ this.name = "AdrSupersedeError";
948
+ this.code = code;
949
+ this.details = details;
950
+ }
951
+ };
952
+ async function supersedeAdr(options) {
953
+ const oldSlug = createSlug2(options.oldName, "oldName");
954
+ createSlug2(options.newTitle, "newTitle");
955
+ const config = await loadRequiredConfig3(options.rootDir);
956
+ const adrDirAbsolute = resolveSafePath(options.rootDir, config.adrDir).absolutePath;
957
+ const old = await findAcceptedAdr(adrDirAbsolute, oldSlug);
958
+ const next = await getNextAdrNumber(adrDirAbsolute);
959
+ const oldRef = old.fileName.replace(/\.md$/u, "");
960
+ const superseding = generateSupersedingAdr({
961
+ adrDir: config.adrDir,
962
+ adrId: next.id,
963
+ title: options.newTitle,
964
+ supersedesRef: oldRef
965
+ });
966
+ const newRef = `${next.id}-${superseding.slug}`;
967
+ const markedOld = old.content.replace(
968
+ /(##\s+Status\r?\n\r?\n)Accepted[^\n]*/u,
969
+ `$1Accepted \u2014 superseded by ${newRef}`
970
+ );
971
+ const writeNew = await write2(options, superseding.path, superseding.content, options.force);
972
+ const oldRelative = `${config.adrDir}/${old.fileName}`;
973
+ const writeOld = await write2(options, oldRelative, markedOld, true);
974
+ return {
975
+ oldRef,
976
+ oldPath: oldRelative,
977
+ newRef,
978
+ newPath: superseding.path,
979
+ dryRun: options.dryRun ?? false,
980
+ writeResult: {
981
+ created: [...writeNew.created, ...writeOld.created],
982
+ overwritten: [...writeNew.overwritten, ...writeOld.overwritten],
983
+ skipped: [...writeNew.skipped, ...writeOld.skipped],
984
+ dryRun: options.dryRun ?? false
985
+ }
986
+ };
987
+ }
988
+ async function findAcceptedAdr(adrDirAbsolute, slug) {
989
+ const pattern = new RegExp(`^ADR-\\d{4,}-${escapeRegExp2(slug)}\\.md$`, "u");
990
+ let entries;
991
+ try {
992
+ entries = await readdir3(adrDirAbsolute, { withFileTypes: true });
993
+ } catch (error) {
994
+ const nodeError = error;
995
+ if (nodeError.code === "ENOENT") {
996
+ throw new AdrSupersedeError("NOT_FOUND", `No accepted ADR found for "${slug}".`);
997
+ }
998
+ throw error;
999
+ }
1000
+ const match = entries.find((entry) => entry.isFile() && pattern.test(entry.name));
1001
+ if (match === void 0) {
1002
+ throw new AdrSupersedeError("NOT_FOUND", `No accepted ADR found for "${slug}".`, [
1003
+ `Looked for an ADR-####-${slug}.md in the ADR directory.`
1004
+ ]);
1005
+ }
1006
+ const content = await readFile3(path5.join(adrDirAbsolute, match.name), "utf8");
1007
+ if (!/(##\s+Status\r?\n\r?\n)Accepted\b/u.test(content)) {
1008
+ throw new AdrSupersedeError(
1009
+ "NOT_ACCEPTED",
1010
+ `ADR ${match.name} is not Accepted, so there is nothing to supersede.`,
1011
+ ["Only an accepted decision can be superseded. Accept it first with `recall adr accept`."]
1012
+ );
1013
+ }
1014
+ return { fileName: match.name, content };
1015
+ }
1016
+ async function write2(options, relativePath, content, force) {
1017
+ const plan = createWritePlan({
1018
+ rootDir: options.rootDir,
1019
+ files: [{ path: relativePath, content }],
1020
+ force
1021
+ });
1022
+ if (plan.hasErrors) {
1023
+ throw new AdrSupersedeError(
1024
+ "WRITE_PLAN_ERROR",
1025
+ "ADR supersede write plan contains errors.",
1026
+ plan.entries.filter(
1027
+ (entry) => entry.action === "error"
1028
+ ).map((entry) => `${entry.path}: ${entry.reason}`)
1029
+ );
1030
+ }
1031
+ return executeWritePlan(plan, { dryRun: options.dryRun });
1032
+ }
1033
+ function formatAdrSupersedeResult(result) {
1034
+ const lines = [
1035
+ result.dryRun ? "Recall OS ADR supersede dry run complete." : "Recall OS ADR supersede complete.",
1036
+ `Superseded: ${result.oldPath} (now marked superseded by ${result.newRef})`,
1037
+ `New decision: ${result.newPath}`
1038
+ ];
1039
+ appendWriteSummary(lines, { dryRun: result.dryRun, writeResult: result.writeResult });
1040
+ if (!result.dryRun) {
1041
+ appendNextSteps(lines, [
1042
+ `Fill ${result.newPath}: Context (what changed), Decision, Alternatives, Consequences.`,
1043
+ `${result.oldRef} stays in history as superseded; update any memory that still relies on it.`,
1044
+ "Run `recall doctor` \u2014 it flags memory that still references the superseded decision."
1045
+ ]);
1046
+ }
1047
+ return `${lines.join("\n")}
1048
+ `;
1049
+ }
1050
+ function createSlug2(name, field) {
1051
+ const withoutPrefix = name.replace(/^ADR-(?:PROPOSED-|\d{4,}-)/iu, "");
1052
+ try {
1053
+ return slugify(withoutPrefix);
1054
+ } catch (error) {
1055
+ if (error instanceof SlugifyError) {
1056
+ throw new AdrSupersedeError("INVALID_ADR_NAME", `Invalid ${field}: ${error.message}`);
1057
+ }
1058
+ throw error;
1059
+ }
1060
+ }
1061
+ function escapeRegExp2(value) {
1062
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
1063
+ }
1064
+ async function loadRequiredConfig3(rootDir) {
1065
+ try {
1066
+ return await loadConfig(rootDir);
1067
+ } catch (error) {
1068
+ if (error instanceof ConfigLoadError || error instanceof ConfigValidationError) {
1069
+ throw new AdrSupersedeError(
1070
+ "CONFIG_REQUIRED",
1071
+ "Recall OS config not found or invalid. Run `recall init` first.",
1072
+ [error.message]
1073
+ );
1074
+ }
1075
+ throw error;
1076
+ }
1077
+ }
1078
+
1079
+ // src/core/generator/generate-feature.ts
1080
+ import path6 from "path";
893
1081
  var featureTemplates = [
894
1082
  {
895
1083
  fileName: "PRD.md",
@@ -1041,7 +1229,7 @@ Pending.
1041
1229
  ];
1042
1230
  function generateFeatureFiles(options) {
1043
1231
  const slug = slugify(options.featureName);
1044
- const featureDir = path5.posix.join(options.featuresDir, `${options.featureId}-${slug}`);
1232
+ const featureDir = path6.posix.join(options.featuresDir, `${options.featureId}-${slug}`);
1045
1233
  const title = titleizeFeatureName(options.featureName);
1046
1234
  const context = createTemplateContext({
1047
1235
  featureId: options.featureId,
@@ -1049,7 +1237,7 @@ function generateFeatureFiles(options) {
1049
1237
  title
1050
1238
  });
1051
1239
  return featureTemplates.map((template) => ({
1052
- path: path5.posix.join(featureDir, template.fileName),
1240
+ path: path6.posix.join(featureDir, template.fileName),
1053
1241
  content: renderTemplate(template.content, context)
1054
1242
  }));
1055
1243
  }
@@ -1058,7 +1246,7 @@ function titleizeFeatureName(featureName) {
1058
1246
  }
1059
1247
 
1060
1248
  // src/core/naming/feature-number.ts
1061
- import { readdir as readdir3 } from "fs/promises";
1249
+ import { readdir as readdir4 } from "fs/promises";
1062
1250
  var FEATURE_FOLDER_PATTERN = /^F-(\d{3,})-([a-z0-9]+(?:-[a-z0-9]+)*)$/u;
1063
1251
  async function getFeatureFolderForSlug(featuresDirAbsolutePath, slug) {
1064
1252
  const existingFolders = await readExistingFeatureFolders(featuresDirAbsolutePath);
@@ -1091,7 +1279,7 @@ function formatFeatureNumber(featureNumber) {
1091
1279
  async function readExistingFeatureFolders(featuresDirAbsolutePath) {
1092
1280
  let entries;
1093
1281
  try {
1094
- entries = await readdir3(featuresDirAbsolutePath, { withFileTypes: true });
1282
+ entries = await readdir4(featuresDirAbsolutePath, { withFileTypes: true });
1095
1283
  } catch (error) {
1096
1284
  const nodeError = error;
1097
1285
  if (nodeError.code === "ENOENT") {
@@ -1130,7 +1318,7 @@ var FeatureCreateError = class extends Error {
1130
1318
  };
1131
1319
  async function createFeature(options) {
1132
1320
  const slug = createFeatureSlug(options.name);
1133
- const config = await loadRequiredConfig3(options.rootDir);
1321
+ const config = await loadRequiredConfig4(options.rootDir);
1134
1322
  const featuresDirPath = resolveSafePath(options.rootDir, config.featuresDir);
1135
1323
  const featureFolder = await getFeatureFolderForSlug(featuresDirPath.absolutePath, slug);
1136
1324
  const files = generateFeatureFiles({
@@ -1189,7 +1377,7 @@ function createFeatureSlug(name) {
1189
1377
  throw error;
1190
1378
  }
1191
1379
  }
1192
- async function loadRequiredConfig3(rootDir) {
1380
+ async function loadRequiredConfig4(rootDir) {
1193
1381
  try {
1194
1382
  return await loadConfig(rootDir);
1195
1383
  } catch (error) {
@@ -1230,8 +1418,8 @@ function createDefaultConfig(overrides = {}) {
1230
1418
 
1231
1419
  // src/core/adopt/inspect-repo.ts
1232
1420
  import { existsSync as existsSync3 } from "fs";
1233
- import { readFile as readFile3, readdir as readdir4 } from "fs/promises";
1234
- import path6 from "path";
1421
+ import { readFile as readFile4, readdir as readdir5 } from "fs/promises";
1422
+ import path7 from "path";
1235
1423
  var FRAMEWORK_SOURCES = {
1236
1424
  "Next.js": "package.json",
1237
1425
  React: "package.json",
@@ -1254,7 +1442,7 @@ var FRAMEWORK_SOURCES = {
1254
1442
  Flutter: "pubspec.yaml"
1255
1443
  };
1256
1444
  async function inspectRepo(rootDir) {
1257
- const has = (relativePath) => existsSync3(path6.join(rootDir, relativePath));
1445
+ const has = (relativePath) => existsSync3(path7.join(rootDir, relativePath));
1258
1446
  const languages = /* @__PURE__ */ new Set();
1259
1447
  const frameworks = /* @__PURE__ */ new Set();
1260
1448
  const pkg = has("package.json") ? await readJson(rootDir, "package.json") : null;
@@ -1468,7 +1656,7 @@ async function findTestFile(rootDir) {
1468
1656
  }
1469
1657
  let entries;
1470
1658
  try {
1471
- entries = await readdir4(dir, { withFileTypes: true });
1659
+ entries = await readdir5(dir, { withFileTypes: true });
1472
1660
  } catch {
1473
1661
  continue;
1474
1662
  }
@@ -1479,10 +1667,10 @@ async function findTestFile(rootDir) {
1479
1667
  }
1480
1668
  if (entry.isDirectory()) {
1481
1669
  if (!TEST_WALK_SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
1482
- stack.push(path6.join(dir, entry.name));
1670
+ stack.push(path7.join(dir, entry.name));
1483
1671
  }
1484
1672
  } else if (TEST_FILE_PATTERNS.some((pattern) => pattern.test(entry.name))) {
1485
- return path6.relative(rootDir, path6.join(dir, entry.name));
1673
+ return path7.relative(rootDir, path7.join(dir, entry.name));
1486
1674
  }
1487
1675
  }
1488
1676
  }
@@ -1506,7 +1694,7 @@ async function readJson(rootDir, relativePath) {
1506
1694
  }
1507
1695
  async function readText(rootDir, relativePath) {
1508
1696
  try {
1509
- return await readFile3(path6.join(rootDir, relativePath), "utf8");
1697
+ return await readFile4(path7.join(rootDir, relativePath), "utf8");
1510
1698
  } catch {
1511
1699
  return "";
1512
1700
  }
@@ -1682,8 +1870,8 @@ function formatList2(values) {
1682
1870
 
1683
1871
  // src/core/doctor/checks/code-reference-check.ts
1684
1872
  import { existsSync as existsSync4 } from "fs";
1685
- import { readFile as readFile4, readdir as readdir5 } from "fs/promises";
1686
- import path7 from "path";
1873
+ import { readFile as readFile5, readdir as readdir6 } from "fs/promises";
1874
+ import path8 from "path";
1687
1875
  var featureFolderPattern = /^F-\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1688
1876
  var FEATURE_DOCS = ["PRD.md", "ARCHITECTURE_IMPACT.md"];
1689
1877
  var MODULE_DOCS = ["MODULE.md", "DECISIONS.md"];
@@ -1700,7 +1888,7 @@ async function checkCodeReferences(context) {
1700
1888
  continue;
1701
1889
  }
1702
1890
  for (const doc of FEATURE_DOCS) {
1703
- const relativePath = path7.posix.join(context.config.featuresDir, folder.name, doc);
1891
+ const relativePath = path8.posix.join(context.config.featuresDir, folder.name, doc);
1704
1892
  findings.push(...await checkDoc(context.rootDir, relativePath));
1705
1893
  }
1706
1894
  }
@@ -1710,7 +1898,7 @@ async function checkCodeReferences(context) {
1710
1898
  continue;
1711
1899
  }
1712
1900
  for (const doc of MODULE_DOCS) {
1713
- const relativePath = path7.posix.join(context.config.modulesDir, folder.name, doc);
1901
+ const relativePath = path8.posix.join(context.config.modulesDir, folder.name, doc);
1714
1902
  findings.push(...await checkDoc(context.rootDir, relativePath));
1715
1903
  }
1716
1904
  }
@@ -1729,7 +1917,7 @@ async function checkDoc(rootDir, relativePath) {
1729
1917
  continue;
1730
1918
  }
1731
1919
  seen.add(reference);
1732
- if (!existsSync4(path7.join(rootDir, reference))) {
1920
+ if (!existsSync4(path8.join(rootDir, reference))) {
1733
1921
  findings.push({
1734
1922
  severity: "warning",
1735
1923
  check: "drift-code-reference",
@@ -1742,7 +1930,7 @@ async function checkDoc(rootDir, relativePath) {
1742
1930
  }
1743
1931
  async function readDirIfExists(rootDir, relativePath) {
1744
1932
  try {
1745
- return await readdir5(path7.join(rootDir, relativePath), { withFileTypes: true });
1933
+ return await readdir6(path8.join(rootDir, relativePath), { withFileTypes: true });
1746
1934
  } catch (error) {
1747
1935
  const nodeError = error;
1748
1936
  if (nodeError.code === "ENOENT") {
@@ -1753,7 +1941,7 @@ async function readDirIfExists(rootDir, relativePath) {
1753
1941
  }
1754
1942
  async function readFileIfExists(rootDir, relativePath) {
1755
1943
  try {
1756
- return await readFile4(path7.join(rootDir, relativePath), "utf8");
1944
+ return await readFile5(path8.join(rootDir, relativePath), "utf8");
1757
1945
  } catch (error) {
1758
1946
  const nodeError = error;
1759
1947
  if (nodeError.code === "ENOENT") {
@@ -1764,12 +1952,12 @@ async function readFileIfExists(rootDir, relativePath) {
1764
1952
  }
1765
1953
 
1766
1954
  // src/core/doctor/checks/config-check.ts
1767
- import { readFile as readFile5 } from "fs/promises";
1955
+ import { readFile as readFile6 } from "fs/promises";
1768
1956
  async function checkConfig(rootDir) {
1769
1957
  const configPath = resolveSafePath(rootDir, CONFIG_PATH);
1770
1958
  let rawConfig;
1771
1959
  try {
1772
- rawConfig = await readFile5(configPath.absolutePath, "utf8");
1960
+ rawConfig = await readFile6(configPath.absolutePath, "utf8");
1773
1961
  } catch (error) {
1774
1962
  const nodeError = error;
1775
1963
  if (nodeError.code === "ENOENT") {
@@ -1832,8 +2020,8 @@ async function checkConfig(rootDir) {
1832
2020
  }
1833
2021
 
1834
2022
  // src/core/doctor/checks/content-check.ts
1835
- import { readFile as readFile6, readdir as readdir6 } from "fs/promises";
1836
- import path8 from "path";
2023
+ import { readFile as readFile7, readdir as readdir7 } from "fs/promises";
2024
+ import path9 from "path";
1837
2025
  var featureFolderPattern2 = /^F-\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1838
2026
  var acceptedAdrPattern = /^ADR-\d{4,}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/u;
1839
2027
  var SECURITY_MODEL_PATH = "docs/20-security/SECURITY_MODEL.md";
@@ -1848,7 +2036,7 @@ async function checkContent(context) {
1848
2036
  (entry) => entry.isDirectory() && featureFolderPattern2.test(entry.name)
1849
2037
  );
1850
2038
  for (const folder of featureFolders) {
1851
- const prdPath = path8.posix.join(context.config.featuresDir, folder.name, "PRD.md");
2039
+ const prdPath = path9.posix.join(context.config.featuresDir, folder.name, "PRD.md");
1852
2040
  const prd = await readFileIfExists2(context.rootDir, prdPath);
1853
2041
  if (prd === void 0) {
1854
2042
  continue;
@@ -1881,7 +2069,7 @@ async function checkContent(context) {
1881
2069
  findings.push(...await checkSecurityDoc(context.rootDir));
1882
2070
  }
1883
2071
  for (const folder of moduleFolders) {
1884
- const modulePath = path8.posix.join(context.config.modulesDir, folder.name, "MODULE.md");
2072
+ const modulePath = path9.posix.join(context.config.modulesDir, folder.name, "MODULE.md");
1885
2073
  const moduleDoc = await readFileIfExists2(context.rootDir, modulePath);
1886
2074
  if (moduleDoc === void 0) {
1887
2075
  continue;
@@ -1959,7 +2147,7 @@ function getSection(content, heading) {
1959
2147
  }
1960
2148
  async function readDirIfExists2(rootDir, relativePath) {
1961
2149
  try {
1962
- return await readdir6(path8.join(rootDir, relativePath), { withFileTypes: true });
2150
+ return await readdir7(path9.join(rootDir, relativePath), { withFileTypes: true });
1963
2151
  } catch (error) {
1964
2152
  const nodeError = error;
1965
2153
  if (nodeError.code === "ENOENT") {
@@ -1970,7 +2158,7 @@ async function readDirIfExists2(rootDir, relativePath) {
1970
2158
  }
1971
2159
  async function readFileIfExists2(rootDir, relativePath) {
1972
2160
  try {
1973
- return await readFile6(path8.join(rootDir, relativePath), "utf8");
2161
+ return await readFile7(path9.join(rootDir, relativePath), "utf8");
1974
2162
  } catch (error) {
1975
2163
  const nodeError = error;
1976
2164
  if (nodeError.code === "ENOENT") {
@@ -1981,8 +2169,8 @@ async function readFileIfExists2(rootDir, relativePath) {
1981
2169
  }
1982
2170
 
1983
2171
  // src/core/doctor/checks/drift-check.ts
1984
- import { readFile as readFile7, readdir as readdir7 } from "fs/promises";
1985
- import path9 from "path";
2172
+ import { readFile as readFile8, readdir as readdir8 } from "fs/promises";
2173
+ import path10 from "path";
1986
2174
  var adrFilePattern = /^ADR-(\d{4,})-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/iu;
1987
2175
  var adrReferencePattern = /ADR-\d{4,}/giu;
1988
2176
  async function checkDrift(context) {
@@ -1999,12 +2187,12 @@ async function loadKnownAdrs(rootDir, adrDir) {
1999
2187
  const known = /* @__PURE__ */ new Map();
2000
2188
  const files = await readMarkdownFiles(rootDir, adrDir);
2001
2189
  for (const file of files) {
2002
- const match = adrFilePattern.exec(path9.basename(file));
2190
+ const match = adrFilePattern.exec(path10.basename(file));
2003
2191
  if (match === null) {
2004
2192
  continue;
2005
2193
  }
2006
2194
  const id = `ADR-${match[1]}`;
2007
- const content = await readFile7(path9.join(rootDir, file), "utf8");
2195
+ const content = await readFile8(path10.join(rootDir, file), "utf8");
2008
2196
  const accepted = sectionContains(content, "Status", /\baccepted\b/iu);
2009
2197
  const existing = known.get(id);
2010
2198
  if (existing === void 0 || !existing.accepted && accepted) {
@@ -2017,7 +2205,7 @@ async function checkReferences(rootDir, referenceDir, knownAdrs) {
2017
2205
  const findings = [];
2018
2206
  const files = await readMarkdownFiles(rootDir, referenceDir);
2019
2207
  for (const file of files) {
2020
- const content = await readFile7(path9.join(rootDir, file), "utf8");
2208
+ const content = await readFile8(path10.join(rootDir, file), "utf8");
2021
2209
  const referenced = /* @__PURE__ */ new Set();
2022
2210
  for (const match of stripCode(content).matchAll(adrReferencePattern)) {
2023
2211
  referenced.add(match[0].toUpperCase());
@@ -2052,7 +2240,7 @@ async function readMarkdownFiles(rootDir, relativeDir) {
2052
2240
  const entries = await readDirIfExists3(rootDir, relativeDir);
2053
2241
  const files = [];
2054
2242
  for (const entry of entries) {
2055
- const childRelative = path9.posix.join(relativeDir, entry.name);
2243
+ const childRelative = path10.posix.join(relativeDir, entry.name);
2056
2244
  if (entry.isDirectory()) {
2057
2245
  files.push(...await readMarkdownFiles(rootDir, childRelative));
2058
2246
  continue;
@@ -2085,7 +2273,7 @@ function getSection2(content, heading) {
2085
2273
  }
2086
2274
  async function readDirIfExists3(rootDir, relativePath) {
2087
2275
  try {
2088
- return await readdir7(path9.join(rootDir, relativePath), { withFileTypes: true });
2276
+ return await readdir8(path10.join(rootDir, relativePath), { withFileTypes: true });
2089
2277
  } catch (error) {
2090
2278
  const nodeError = error;
2091
2279
  if (nodeError.code === "ENOENT") {
@@ -2096,8 +2284,8 @@ async function readDirIfExists3(rootDir, relativePath) {
2096
2284
  }
2097
2285
 
2098
2286
  // src/core/doctor/checks/memory-integrity-check.ts
2099
- import { lstat as lstat2, readFile as readFile8, readdir as readdir8 } from "fs/promises";
2100
- import path10 from "path";
2287
+ import { lstat as lstat2, readFile as readFile9, readdir as readdir9 } from "fs/promises";
2288
+ import path11 from "path";
2101
2289
 
2102
2290
  // src/core/adr/adr-sections.ts
2103
2291
  var REQUIRED_ADR_SECTIONS = [
@@ -2161,7 +2349,7 @@ async function checkFeatureFolders(rootDir, featuresDir) {
2161
2349
  );
2162
2350
  for (const featureFolder of featureFolders) {
2163
2351
  for (const requiredDoc of requiredFeatureDocs) {
2164
- const filePath = path10.posix.join(featuresDir, featureFolder.name, requiredDoc);
2352
+ const filePath = path11.posix.join(featuresDir, featureFolder.name, requiredDoc);
2165
2353
  if (!await isFile(rootDir, filePath)) {
2166
2354
  findings.push({
2167
2355
  severity: "error",
@@ -2185,7 +2373,7 @@ async function checkModuleFolders(rootDir, modulesDir) {
2185
2373
  const moduleFolders = entries.filter((entry) => entry.isDirectory());
2186
2374
  for (const moduleFolder of moduleFolders) {
2187
2375
  for (const requiredDoc of requiredModuleDocs) {
2188
- const filePath = path10.posix.join(modulesDir, moduleFolder.name, requiredDoc);
2376
+ const filePath = path11.posix.join(modulesDir, moduleFolder.name, requiredDoc);
2189
2377
  if (!await isFile(rootDir, filePath)) {
2190
2378
  findings.push({
2191
2379
  severity: "error",
@@ -2208,8 +2396,8 @@ async function checkAdrFiles(rootDir, adrDir) {
2208
2396
  const entries = await readDirIfExists4(rootDir, adrDir);
2209
2397
  const adrFiles = entries.filter((entry) => entry.isFile() && adrFilePattern2.test(entry.name));
2210
2398
  for (const adrFile of adrFiles) {
2211
- const filePath = path10.posix.join(adrDir, adrFile.name);
2212
- const content = await readFile8(path10.join(rootDir, filePath), "utf8");
2399
+ const filePath = path11.posix.join(adrDir, adrFile.name);
2400
+ const content = await readFile9(path11.join(rootDir, filePath), "utf8");
2213
2401
  for (const requiredSection of requiredAdrSections) {
2214
2402
  if (!content.includes(requiredSection)) {
2215
2403
  findings.push({
@@ -2230,7 +2418,7 @@ async function checkAdrFiles(rootDir, adrDir) {
2230
2418
  }
2231
2419
  async function readDirIfExists4(rootDir, relativePath) {
2232
2420
  try {
2233
- return await readdir8(path10.join(rootDir, relativePath), { withFileTypes: true });
2421
+ return await readdir9(path11.join(rootDir, relativePath), { withFileTypes: true });
2234
2422
  } catch (error) {
2235
2423
  const nodeError = error;
2236
2424
  if (nodeError.code === "ENOENT") {
@@ -2241,7 +2429,7 @@ async function readDirIfExists4(rootDir, relativePath) {
2241
2429
  }
2242
2430
  async function isFile(rootDir, relativePath) {
2243
2431
  try {
2244
- return (await lstat2(path10.join(rootDir, relativePath))).isFile();
2432
+ return (await lstat2(path11.join(rootDir, relativePath))).isFile();
2245
2433
  } catch (error) {
2246
2434
  const nodeError = error;
2247
2435
  if (nodeError.code === "ENOENT") {
@@ -2253,7 +2441,7 @@ async function isFile(rootDir, relativePath) {
2253
2441
 
2254
2442
  // src/core/doctor/checks/required-files-check.ts
2255
2443
  import { lstat as lstat3 } from "fs/promises";
2256
- import path11 from "path";
2444
+ import path12 from "path";
2257
2445
  var rootFiles = ["AGENTS.md", "CLAUDE.md"];
2258
2446
  var requiredDocs = [
2259
2447
  "00-product/PRD.md",
@@ -2280,12 +2468,12 @@ async function checkRequiredFiles(context) {
2280
2468
  }
2281
2469
  }
2282
2470
  for (const relativeDocPath of requiredDocs) {
2283
- const filePath = path11.posix.join(docsDir, relativeDocPath);
2471
+ const filePath = path12.posix.join(docsDir, relativeDocPath);
2284
2472
  if (!await isFile2(context.rootDir, filePath)) {
2285
2473
  findings.push(missingFile(filePath, "required-docs"));
2286
2474
  }
2287
2475
  }
2288
- const adrIndexPath = path11.posix.join(context.config?.adrDir ?? "docs/adrs", "README.md");
2476
+ const adrIndexPath = path12.posix.join(context.config?.adrDir ?? "docs/adrs", "README.md");
2289
2477
  if (!await isFile2(context.rootDir, adrIndexPath)) {
2290
2478
  findings.push(missingFile(adrIndexPath, "required-docs"));
2291
2479
  }
@@ -2311,7 +2499,7 @@ async function checkRequiredFiles(context) {
2311
2499
  }
2312
2500
  async function isFile2(rootDir, relativePath) {
2313
2501
  try {
2314
- return (await lstat3(path11.join(rootDir, relativePath))).isFile();
2502
+ return (await lstat3(path12.join(rootDir, relativePath))).isFile();
2315
2503
  } catch (error) {
2316
2504
  const nodeError = error;
2317
2505
  if (nodeError.code === "ENOENT") {
@@ -2322,7 +2510,7 @@ async function isFile2(rootDir, relativePath) {
2322
2510
  }
2323
2511
  async function isDirectory(rootDir, relativePath) {
2324
2512
  try {
2325
- return (await lstat3(path11.join(rootDir, relativePath))).isDirectory();
2513
+ return (await lstat3(path12.join(rootDir, relativePath))).isDirectory();
2326
2514
  } catch (error) {
2327
2515
  const nodeError = error;
2328
2516
  if (nodeError.code === "ENOENT") {
@@ -2341,8 +2529,8 @@ function missingFile(pathValue, check) {
2341
2529
  }
2342
2530
 
2343
2531
  // src/core/doctor/checks/standards-check.ts
2344
- import { lstat as lstat4, readFile as readFile9, readdir as readdir9 } from "fs/promises";
2345
- import path12 from "path";
2532
+ import { lstat as lstat4, readFile as readFile10, readdir as readdir10 } from "fs/promises";
2533
+ import path13 from "path";
2346
2534
  var featureFolderPattern4 = /^F-\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
2347
2535
  var adrFilePattern3 = /^ADR-\d{4,}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/u;
2348
2536
  var securitySensitivePattern = /\b(auth|authentication|authorization|secrets?|storage|networking?|telemetry|file writes?|write policy|dependencies?|mcp|ai api|cloud|runtime)\b/iu;
@@ -2362,10 +2550,10 @@ async function checkFeatureStandards(rootDir, featuresDir) {
2362
2550
  (entry) => entry.isDirectory() && featureFolderPattern4.test(entry.name)
2363
2551
  );
2364
2552
  for (const featureFolder of featureFolders) {
2365
- const featureDir = path12.posix.join(featuresDir, featureFolder.name);
2366
- const completionReportPath = path12.posix.join(featureDir, "COMPLETION_REPORT.md");
2367
- const reviewPath = path12.posix.join(featureDir, "REVIEW.md");
2368
- const architectureImpactPath = path12.posix.join(featureDir, "ARCHITECTURE_IMPACT.md");
2553
+ const featureDir = path13.posix.join(featuresDir, featureFolder.name);
2554
+ const completionReportPath = path13.posix.join(featureDir, "COMPLETION_REPORT.md");
2555
+ const reviewPath = path13.posix.join(featureDir, "REVIEW.md");
2556
+ const architectureImpactPath = path13.posix.join(featureDir, "ARCHITECTURE_IMPACT.md");
2369
2557
  const completionReport = await readFileIfExists3(rootDir, completionReportPath);
2370
2558
  const review = await readFileIfExists3(rootDir, reviewPath);
2371
2559
  const architectureImpact = await readFileIfExists3(rootDir, architectureImpactPath);
@@ -2415,8 +2603,8 @@ async function checkAdrStandards(rootDir, adrDir) {
2415
2603
  const entries = await readDirIfExists5(rootDir, adrDir);
2416
2604
  const adrFiles = entries.filter((entry) => entry.isFile() && adrFilePattern3.test(entry.name));
2417
2605
  for (const adrFile of adrFiles) {
2418
- const adrPath = path12.posix.join(adrDir, adrFile.name);
2419
- const content = await readFile9(path12.join(rootDir, adrPath), "utf8");
2606
+ const adrPath = path13.posix.join(adrDir, adrFile.name);
2607
+ const content = await readFile10(path13.join(rootDir, adrPath), "utf8");
2420
2608
  const isAccepted = sectionContains2(content, "Status", /\baccepted\b/iu);
2421
2609
  if (!hasMeaningfulSection(content, "Consequences")) {
2422
2610
  findings.push({
@@ -2500,7 +2688,7 @@ function isPlaceholder(value) {
2500
2688
  }
2501
2689
  async function readDirIfExists5(rootDir, relativePath) {
2502
2690
  try {
2503
- return await readdir9(path12.join(rootDir, relativePath), { withFileTypes: true });
2691
+ return await readdir10(path13.join(rootDir, relativePath), { withFileTypes: true });
2504
2692
  } catch (error) {
2505
2693
  const nodeError = error;
2506
2694
  if (nodeError.code === "ENOENT") {
@@ -2514,7 +2702,7 @@ async function readFileIfExists3(rootDir, relativePath) {
2514
2702
  if (!await isFile3(rootDir, relativePath)) {
2515
2703
  return void 0;
2516
2704
  }
2517
- return await readFile9(path12.join(rootDir, relativePath), "utf8");
2705
+ return await readFile10(path13.join(rootDir, relativePath), "utf8");
2518
2706
  } catch (error) {
2519
2707
  const nodeError = error;
2520
2708
  if (nodeError.code === "ENOENT") {
@@ -2525,7 +2713,7 @@ async function readFileIfExists3(rootDir, relativePath) {
2525
2713
  }
2526
2714
  async function isFile3(rootDir, relativePath) {
2527
2715
  try {
2528
- return (await lstat4(path12.join(rootDir, relativePath))).isFile();
2716
+ return (await lstat4(path13.join(rootDir, relativePath))).isFile();
2529
2717
  } catch (error) {
2530
2718
  const nodeError = error;
2531
2719
  if (nodeError.code === "ENOENT") {
@@ -2535,6 +2723,110 @@ async function isFile3(rootDir, relativePath) {
2535
2723
  }
2536
2724
  }
2537
2725
 
2726
+ // src/core/doctor/checks/superseded-check.ts
2727
+ import { readFile as readFile11, readdir as readdir11 } from "fs/promises";
2728
+ import path14 from "path";
2729
+ var adrFilePattern4 = /^ADR-(\d{4,})-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/iu;
2730
+ var adrReferencePattern2 = /ADR-\d{4,}/giu;
2731
+ async function checkSuperseded(context) {
2732
+ if (context.config === void 0) {
2733
+ return [];
2734
+ }
2735
+ const supersededIds = await loadSupersededAdrIds(context.rootDir, context.config.adrDir);
2736
+ if (supersededIds.size === 0) {
2737
+ return [];
2738
+ }
2739
+ const findings = [];
2740
+ findings.push(
2741
+ ...await checkReferences2(context.rootDir, context.config.featuresDir, supersededIds)
2742
+ );
2743
+ findings.push(
2744
+ ...await checkReferences2(context.rootDir, context.config.modulesDir, supersededIds)
2745
+ );
2746
+ return findings;
2747
+ }
2748
+ async function loadSupersededAdrIds(rootDir, adrDir) {
2749
+ const superseded = /* @__PURE__ */ new Set();
2750
+ const files = await readMarkdownFiles2(rootDir, adrDir);
2751
+ for (const file of files) {
2752
+ const match = adrFilePattern4.exec(path14.basename(file));
2753
+ if (match === null) {
2754
+ continue;
2755
+ }
2756
+ const content = await readFile11(path14.join(rootDir, file), "utf8");
2757
+ if (statusContains(content, /superseded\s+by/iu)) {
2758
+ superseded.add(`ADR-${match[1]}`.toUpperCase());
2759
+ }
2760
+ }
2761
+ return superseded;
2762
+ }
2763
+ async function checkReferences2(rootDir, referenceDir, supersededIds) {
2764
+ const findings = [];
2765
+ const files = await readMarkdownFiles2(rootDir, referenceDir);
2766
+ for (const file of files) {
2767
+ const content = await readFile11(path14.join(rootDir, file), "utf8");
2768
+ const referenced = /* @__PURE__ */ new Set();
2769
+ for (const match of stripCode2(content).matchAll(adrReferencePattern2)) {
2770
+ referenced.add(match[0].toUpperCase());
2771
+ }
2772
+ for (const id of referenced) {
2773
+ if (supersededIds.has(id)) {
2774
+ findings.push({
2775
+ severity: "warning",
2776
+ check: "superseded-reference",
2777
+ message: `Repository memory references ${id}, which has been superseded \u2014 update it to the current decision.`,
2778
+ path: file
2779
+ });
2780
+ }
2781
+ }
2782
+ }
2783
+ return findings;
2784
+ }
2785
+ function statusContains(content, pattern) {
2786
+ const lines = content.split(/\r?\n/u);
2787
+ const startIndex = lines.findIndex((line) => line.trim().toLowerCase() === "## status");
2788
+ if (startIndex === -1) {
2789
+ return false;
2790
+ }
2791
+ const body = [];
2792
+ for (let index = startIndex + 1; index < lines.length; index += 1) {
2793
+ if (/^##\s+/u.test(lines[index])) {
2794
+ break;
2795
+ }
2796
+ body.push(lines[index]);
2797
+ }
2798
+ return pattern.test(body.join("\n"));
2799
+ }
2800
+ function stripCode2(content) {
2801
+ return content.replace(/```[\s\S]*?```/gu, " ").replace(/~~~[\s\S]*?~~~/gu, " ").replace(/`[^`]*`/gu, " ");
2802
+ }
2803
+ async function readMarkdownFiles2(rootDir, relativeDir) {
2804
+ const entries = await readDirIfExists6(rootDir, relativeDir);
2805
+ const files = [];
2806
+ for (const entry of entries) {
2807
+ const childRelative = path14.posix.join(relativeDir, entry.name);
2808
+ if (entry.isDirectory()) {
2809
+ files.push(...await readMarkdownFiles2(rootDir, childRelative));
2810
+ continue;
2811
+ }
2812
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
2813
+ files.push(childRelative);
2814
+ }
2815
+ }
2816
+ return files;
2817
+ }
2818
+ async function readDirIfExists6(rootDir, relativePath) {
2819
+ try {
2820
+ return await readdir11(path14.join(rootDir, relativePath), { withFileTypes: true });
2821
+ } catch (error) {
2822
+ const nodeError = error;
2823
+ if (nodeError.code === "ENOENT") {
2824
+ return [];
2825
+ }
2826
+ throw error;
2827
+ }
2828
+ }
2829
+
2538
2830
  // src/core/doctor/doctor-check.ts
2539
2831
  async function runDoctor(rootDir) {
2540
2832
  const findings = [];
@@ -2551,6 +2843,7 @@ async function runDoctor(rootDir) {
2551
2843
  findings.push(...await checkDrift(context));
2552
2844
  findings.push(...await checkContent(context));
2553
2845
  findings.push(...await checkCodeReferences(context));
2846
+ findings.push(...await checkSuperseded(context));
2554
2847
  }
2555
2848
  return createDoctorReport(findings);
2556
2849
  }
@@ -2628,10 +2921,10 @@ function formatDoctorResult(result) {
2628
2921
 
2629
2922
  // src/commands/init.ts
2630
2923
  import { existsSync as existsSync6 } from "fs";
2631
- import path15 from "path";
2924
+ import path17 from "path";
2632
2925
 
2633
2926
  // src/core/generator/generate-init.ts
2634
- import path13 from "path";
2927
+ import path15 from "path";
2635
2928
  var neutralTemplates = [
2636
2929
  {
2637
2930
  path: "AGENTS.md",
@@ -2650,6 +2943,32 @@ Required reading:
2650
2943
  - \`docs/60-engineering/ENGINEERING_STANDARDS.md\`
2651
2944
 
2652
2945
  Repository rules override model preferences. If instructions conflict, stop and report the conflict.
2946
+
2947
+ ## Recall OS commands
2948
+
2949
+ This repository is maintained with the Recall OS CLI. Use these commands yourself as you work \u2014 do not
2950
+ ask the human to run them, and do not search the web for them (this is a project-local tool):
2951
+
2952
+ - \`recall doctor\` \u2014 validate repository memory; run it before claiming any work is complete.
2953
+ - \`recall feature create <name>\` \u2014 scaffold feature memory before non-trivial feature work.
2954
+ - \`recall adr create <title>\` \u2014 propose a decision; \`recall adr accept <name>\` accepts it.
2955
+ - \`recall adr supersede <old> <new-title>\` \u2014 record a changed decision (never overwrite an accepted ADR).
2956
+ - \`recall module create <name>\` \u2014 scaffold module memory for a new responsibility boundary.
2957
+ - \`recall mcp add <server>\` \u2014 capture an MCP tool's context into memory, offline.
2958
+
2959
+ Full command reference: \`docs/ai/RECALL_COMMANDS.md\`.
2960
+
2961
+ ## Changing an accepted decision
2962
+
2963
+ Before changing anything an accepted ADR governs (framework, database, auth, API shape, and similar):
2964
+
2965
+ 1. Check \`docs/adrs/\` for an accepted ADR that covers it.
2966
+ 2. If your change contradicts one, stop and confirm with a human first \u2014 do not silently change the
2967
+ code and leave the ADR saying the opposite.
2968
+ 3. Record the change as a new decision with \`recall adr supersede <old> <new-title>\`. That supersedes
2969
+ the old ADR instead of overwriting history, so the reasoning stays auditable.
2970
+
2971
+ Repository memory is only trustworthy if decisions change through this trail, not silently.
2653
2972
  `
2654
2973
  },
2655
2974
  {
@@ -2686,10 +3005,17 @@ Before non-trivial work:
2686
3005
  - Read \`AGENTS.md\` and the docs it routes to.
2687
3006
  - Accepted decisions live in \`docs/adrs/\`; module memory lives in \`docs/30-modules/\`.
2688
3007
  - If an instruction conflicts with accepted repository memory, stop and report the conflict.
3008
+ - Before changing what an accepted ADR governs, confirm with a human and record it with
3009
+ \`recall adr supersede <old> <new-title>\` \u2014 never silently contradict an accepted decision.
2689
3010
 
2690
3011
  Source-of-truth order: accepted ADRs and repository decisions, then architecture docs, engineering
2691
3012
  standards, the current PRD, security and testing docs, module docs, feature plans, then chat history.
2692
3013
 
3014
+ Recall OS commands \u2014 use these yourself (do not web-search this project-local CLI): \`recall doctor\`,
3015
+ \`recall feature create <name>\`, \`recall adr create <title>\` then \`recall adr accept <name>\`,
3016
+ \`recall adr supersede <old> <new-title>\`, \`recall module create <name>\`, \`recall mcp add <server>\`.
3017
+ Full reference: \`docs/ai/RECALL_COMMANDS.md\`.
3018
+
2693
3019
  Before claiming work is complete, run \`recall doctor\` and fix reported errors.
2694
3020
  `
2695
3021
  },
@@ -2894,6 +3220,15 @@ Baseline rules:
2894
3220
  AI agents must follow repository memory over model preference.
2895
3221
 
2896
3222
  If a request conflicts with accepted repository memory or engineering standards, stop and report the conflict.
3223
+
3224
+ ## Changing an accepted decision
3225
+
3226
+ When work would change something an accepted ADR governs:
3227
+
3228
+ 1. Find the accepted ADR in \`docs/adrs/\` that covers it.
3229
+ 2. If the change contradicts it, stop and confirm with a human before changing the code.
3230
+ 3. Record the new decision with \`recall adr supersede <old> <new-title>\` so the old ADR is marked
3231
+ superseded and the reasoning is preserved, instead of silently editing or contradicting it.
2897
3232
  `
2898
3233
  },
2899
3234
  {
@@ -3027,6 +3362,17 @@ Options:
3027
3362
  - \`--dry-run\`: show planned writes without writing files.
3028
3363
  - \`--force\`: overwrite existing files explicitly.
3029
3364
 
3365
+ ### \`recall adr supersede <old> <new-title>\`
3366
+
3367
+ Record a changed decision. Marks an accepted ADR as \`Accepted \u2014 superseded by ADR-####\` and creates a
3368
+ new accepted ADR that declares what it supersedes, so the reasoning trail stays auditable instead of
3369
+ being overwritten. Doctor then warns about any memory still referencing the superseded decision.
3370
+
3371
+ Options:
3372
+
3373
+ - \`--dry-run\`: show planned writes without writing files.
3374
+ - \`--force\`: overwrite existing files explicitly.
3375
+
3030
3376
  ### \`recall module create <name>\`
3031
3377
 
3032
3378
  Create module memory docs under the configured modules directory.
@@ -3042,7 +3388,8 @@ Check whether repository memory is structurally healthy enough for AI-assisted w
3042
3388
  engineering evidence is present, and whether memory references decisions that exist and are accepted.
3043
3389
 
3044
3390
  Doctor also runs deterministic drift checks: feature or module memory that references a missing ADR
3045
- is an error, and memory that references a not-yet-accepted ADR is a warning.
3391
+ is an error, memory that references a not-yet-accepted ADR is a warning, and memory that still
3392
+ references a superseded decision is a warning.
3046
3393
 
3047
3394
  Exit codes:
3048
3395
 
@@ -3129,7 +3476,7 @@ jobs:
3129
3476
  }
3130
3477
  ];
3131
3478
  function generateInitFiles(options) {
3132
- const repositoryName = path13.basename(path13.resolve(options.rootDir)) || "repository";
3479
+ const repositoryName = path15.basename(path15.resolve(options.rootDir)) || "repository";
3133
3480
  const context = createTemplateContext({ repositoryName });
3134
3481
  const files = neutralTemplates.map((template) => ({
3135
3482
  path: template.path,
@@ -3156,17 +3503,17 @@ function generatePresetFiles(preset) {
3156
3503
 
3157
3504
  // src/core/hooks/detect-gates.ts
3158
3505
  import { existsSync as existsSync5 } from "fs";
3159
- import { readFile as readFile10 } from "fs/promises";
3160
- import path14 from "path";
3506
+ import { readFile as readFile12 } from "fs/promises";
3507
+ import path16 from "path";
3161
3508
  var KNOWN_SCRIPTS = ["test", "typecheck", "lint"];
3162
3509
  async function detectPreCommitGates(rootDir) {
3163
- const packageJsonPath = path14.join(rootDir, "package.json");
3510
+ const packageJsonPath = path16.join(rootDir, "package.json");
3164
3511
  if (!existsSync5(packageJsonPath)) {
3165
3512
  return [];
3166
3513
  }
3167
3514
  let scripts;
3168
3515
  try {
3169
- const raw = await readFile10(packageJsonPath, "utf8");
3516
+ const raw = await readFile12(packageJsonPath, "utf8");
3170
3517
  const parsed = JSON.parse(raw);
3171
3518
  scripts = parsed.scripts ?? {};
3172
3519
  } catch {
@@ -3181,10 +3528,10 @@ async function detectPreCommitGates(rootDir) {
3181
3528
  );
3182
3529
  }
3183
3530
  function detectPackageManager2(rootDir) {
3184
- if (existsSync5(path14.join(rootDir, "pnpm-lock.yaml"))) {
3531
+ if (existsSync5(path16.join(rootDir, "pnpm-lock.yaml"))) {
3185
3532
  return "pnpm";
3186
3533
  }
3187
- if (existsSync5(path14.join(rootDir, "yarn.lock"))) {
3534
+ if (existsSync5(path16.join(rootDir, "yarn.lock"))) {
3188
3535
  return "yarn";
3189
3536
  }
3190
3537
  return "npm";
@@ -3204,7 +3551,7 @@ function renderSessionStartHook() {
3204
3551
  adrs=$(ls docs/adrs/ADR-*.md 2>/dev/null | sed 's|.*/||;s|\\.md$||' | tr '\\n' ' ')
3205
3552
  modules=$(ls -d docs/30-modules/*/ 2>/dev/null | sed 's|docs/30-modules/||;s|/$||' | tr '\\n' ' ')
3206
3553
 
3207
- context="Recall OS repository memory is the source of truth over chat history. Before non-trivial work, read AGENTS.md and the docs it routes to; repository rules override model preference. Accepted ADRs (docs/adrs/): \${adrs:-none yet}. Modules (docs/30-modules/): \${modules:-none yet}. Run 'recall doctor' before claiming work complete."
3554
+ context="Recall OS repository memory is the source of truth over chat history. Before non-trivial work, read AGENTS.md and the docs it routes to; repository rules override model preference. Accepted ADRs (docs/adrs/): \${adrs:-none yet}. Modules (docs/30-modules/): \${modules:-none yet}. Use the Recall OS CLI commands listed in AGENTS.md (recall feature/adr/module create, recall adr accept and supersede, recall doctor) yourself; do not web-search them. Run 'recall doctor' before claiming work complete."
3208
3555
 
3209
3556
  printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\\n' "$context"
3210
3557
  `;
@@ -4689,8 +5036,8 @@ function parsePreset(value) {
4689
5036
  if (!result.success) {
4690
5037
  throw new PresetValidationError(
4691
5038
  result.error.issues.map((issue) => {
4692
- const path17 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
4693
- return `${path17}${issue.message}`;
5039
+ const path19 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
5040
+ return `${path19}${issue.message}`;
4694
5041
  })
4695
5042
  );
4696
5043
  }
@@ -5379,7 +5726,7 @@ var InitError = class extends Error {
5379
5726
  }
5380
5727
  };
5381
5728
  async function initProject(options) {
5382
- if (options.force === true && options.reinit !== true && existsSync6(path15.join(options.rootDir, CONFIG_PATH))) {
5729
+ if (options.force === true && options.reinit !== true && existsSync6(path17.join(options.rootDir, CONFIG_PATH))) {
5383
5730
  throw new InitError(
5384
5731
  "EXISTING_INSTALLATION",
5385
5732
  "Refusing to re-initialize an existing Recall OS installation.",
@@ -5743,7 +6090,7 @@ async function loadConfigOrDefault2(rootDir) {
5743
6090
  }
5744
6091
 
5745
6092
  // src/core/generator/generate-module.ts
5746
- import path16 from "path";
6093
+ import path18 from "path";
5747
6094
  var moduleTemplates = [
5748
6095
  {
5749
6096
  fileName: "MODULE.md",
@@ -5816,14 +6163,14 @@ Record durable module decisions here.
5816
6163
  ];
5817
6164
  function generateModuleFiles(options) {
5818
6165
  const slug = slugify(options.moduleName);
5819
- const moduleDir = path16.posix.join(options.modulesDir, slug);
6166
+ const moduleDir = path18.posix.join(options.modulesDir, slug);
5820
6167
  const title = titleizeModuleName(options.moduleName);
5821
6168
  const context = createTemplateContext({
5822
6169
  slug,
5823
6170
  title
5824
6171
  });
5825
6172
  return moduleTemplates.map((template) => ({
5826
- path: path16.posix.join(moduleDir, template.fileName),
6173
+ path: path18.posix.join(moduleDir, template.fileName),
5827
6174
  content: renderTemplate(template.content, context)
5828
6175
  }));
5829
6176
  }
@@ -5844,7 +6191,7 @@ var ModuleCreateError = class extends Error {
5844
6191
  };
5845
6192
  async function createModule(options) {
5846
6193
  const slug = createModuleSlug(options.name);
5847
- const config = await loadRequiredConfig4(options.rootDir);
6194
+ const config = await loadRequiredConfig5(options.rootDir);
5848
6195
  const files = generateModuleFiles({
5849
6196
  modulesDir: config.modulesDir,
5850
6197
  moduleName: options.name
@@ -5898,7 +6245,7 @@ function createModuleSlug(name) {
5898
6245
  throw error;
5899
6246
  }
5900
6247
  }
5901
- async function loadRequiredConfig4(rootDir) {
6248
+ async function loadRequiredConfig5(rootDir) {
5902
6249
  try {
5903
6250
  return await loadConfig(rootDir);
5904
6251
  } catch (error) {
@@ -6072,6 +6419,20 @@ function createCliProgram(io = {}, state = { exitCode: 0 }) {
6072
6419
  });
6073
6420
  stdout.write(formatAdrAcceptResult(result));
6074
6421
  });
6422
+ adrCommand.command("supersede").description(
6423
+ "Record a changed decision: mark an accepted ADR superseded by a new accepted ADR."
6424
+ ).argument("<old>", "Accepted ADR name or slug being superseded, e.g. database-postgres.").argument("<new-title>", "Title of the new decision that replaces it.").option("--dry-run", "Show planned writes without writing files.").option("--force", "Overwrite existing files explicitly.").action(
6425
+ async (oldName, newTitle, options) => {
6426
+ const result = await supersedeAdr({
6427
+ rootDir: cwd,
6428
+ oldName,
6429
+ newTitle,
6430
+ dryRun: options.dryRun,
6431
+ force: options.force
6432
+ });
6433
+ stdout.write(formatAdrSupersedeResult(result));
6434
+ }
6435
+ );
6075
6436
  const moduleCommand = program.command("module").description("Manage Recall OS module memory.");
6076
6437
  moduleCommand.command("create").description("Create module memory docs.").argument("<name>", "Module name.").option("--dry-run", "Show planned writes without writing files.").option("--force", "Overwrite existing files explicitly.").action(async (name, options) => {
6077
6438
  const result = await createModule({
@@ -6165,6 +6526,15 @@ async function main(argv = process.argv.slice(2), io = {}) {
6165
6526
  `);
6166
6527
  for (const detail of error.details) {
6167
6528
  stderr.write(`- ${detail}
6529
+ `);
6530
+ }
6531
+ return 1;
6532
+ }
6533
+ if (error instanceof AdrSupersedeError) {
6534
+ stderr.write(`${error.message}
6535
+ `);
6536
+ for (const detail of error.details) {
6537
+ stderr.write(`- ${detail}
6168
6538
  `);
6169
6539
  }
6170
6540
  return 1;