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/cli.js CHANGED
@@ -134,8 +134,8 @@ function parseConfig(value) {
134
134
  if (!result.success) {
135
135
  throw new ConfigValidationError(
136
136
  result.error.issues.map((issue) => {
137
- const path17 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
138
- return `${path17}${issue.message}`;
137
+ const path19 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
138
+ return `${path19}${issue.message}`;
139
139
  })
140
140
  );
141
141
  }
@@ -800,6 +800,54 @@ function generateAdrFile(options) {
800
800
  }
801
801
  ];
802
802
  }
803
+ var supersedingAdrTemplate = `# {{adrId}}: {{title}}
804
+
805
+ ## Status
806
+
807
+ Accepted
808
+
809
+ ## Supersedes
810
+
811
+ - {{supersedesRef}}
812
+
813
+ ## Context
814
+
815
+ What changed, and why the previous decision no longer holds?
816
+
817
+ ## Decision
818
+
819
+ What is the new decision?
820
+
821
+ ## Alternatives Considered
822
+
823
+ What other options were considered?
824
+
825
+ ## Consequences
826
+
827
+ What improves, what worsens, and what risks remain?
828
+
829
+ ## Related Documents
830
+
831
+ - PRD:
832
+ - Architecture:
833
+ - Security:
834
+ - Feature:
835
+ `;
836
+ function generateSupersedingAdr(options) {
837
+ const slug = slugify(options.title);
838
+ const title = titleizeAdrTitle(options.title);
839
+ const context = createTemplateContext({
840
+ adrId: options.adrId,
841
+ slug,
842
+ title,
843
+ supersedesRef: options.supersedesRef
844
+ });
845
+ return {
846
+ path: path4.posix.join(options.adrDir, `${options.adrId}-${slug}.md`),
847
+ content: renderTemplate(supersedingAdrTemplate, context),
848
+ slug
849
+ };
850
+ }
803
851
  function titleizeAdrTitle(title) {
804
852
  return title.trim().replace(/[-_]+/gu, " ").replace(/\s+/gu, " ").replace(/\b\w/gu, (character) => character.toUpperCase());
805
853
  }
@@ -890,8 +938,148 @@ async function loadRequiredConfig2(rootDir) {
890
938
  }
891
939
  }
892
940
 
893
- // src/core/generator/generate-feature.ts
941
+ // src/commands/adr/supersede.ts
942
+ import { readFile as readFile3, readdir as readdir3 } from "fs/promises";
894
943
  import path5 from "path";
944
+ var AdrSupersedeError = class extends Error {
945
+ code;
946
+ details;
947
+ constructor(code, message, details = []) {
948
+ super(message);
949
+ this.name = "AdrSupersedeError";
950
+ this.code = code;
951
+ this.details = details;
952
+ }
953
+ };
954
+ async function supersedeAdr(options) {
955
+ const oldSlug = createSlug2(options.oldName, "oldName");
956
+ createSlug2(options.newTitle, "newTitle");
957
+ const config = await loadRequiredConfig3(options.rootDir);
958
+ const adrDirAbsolute = resolveSafePath(options.rootDir, config.adrDir).absolutePath;
959
+ const old = await findAcceptedAdr(adrDirAbsolute, oldSlug);
960
+ const next = await getNextAdrNumber(adrDirAbsolute);
961
+ const oldRef = old.fileName.replace(/\.md$/u, "");
962
+ const superseding = generateSupersedingAdr({
963
+ adrDir: config.adrDir,
964
+ adrId: next.id,
965
+ title: options.newTitle,
966
+ supersedesRef: oldRef
967
+ });
968
+ const newRef = `${next.id}-${superseding.slug}`;
969
+ const markedOld = old.content.replace(
970
+ /(##\s+Status\r?\n\r?\n)Accepted[^\n]*/u,
971
+ `$1Accepted \u2014 superseded by ${newRef}`
972
+ );
973
+ const writeNew = await write2(options, superseding.path, superseding.content, options.force);
974
+ const oldRelative = `${config.adrDir}/${old.fileName}`;
975
+ const writeOld = await write2(options, oldRelative, markedOld, true);
976
+ return {
977
+ oldRef,
978
+ oldPath: oldRelative,
979
+ newRef,
980
+ newPath: superseding.path,
981
+ dryRun: options.dryRun ?? false,
982
+ writeResult: {
983
+ created: [...writeNew.created, ...writeOld.created],
984
+ overwritten: [...writeNew.overwritten, ...writeOld.overwritten],
985
+ skipped: [...writeNew.skipped, ...writeOld.skipped],
986
+ dryRun: options.dryRun ?? false
987
+ }
988
+ };
989
+ }
990
+ async function findAcceptedAdr(adrDirAbsolute, slug) {
991
+ const pattern = new RegExp(`^ADR-\\d{4,}-${escapeRegExp2(slug)}\\.md$`, "u");
992
+ let entries;
993
+ try {
994
+ entries = await readdir3(adrDirAbsolute, { withFileTypes: true });
995
+ } catch (error) {
996
+ const nodeError = error;
997
+ if (nodeError.code === "ENOENT") {
998
+ throw new AdrSupersedeError("NOT_FOUND", `No accepted ADR found for "${slug}".`);
999
+ }
1000
+ throw error;
1001
+ }
1002
+ const match = entries.find((entry) => entry.isFile() && pattern.test(entry.name));
1003
+ if (match === void 0) {
1004
+ throw new AdrSupersedeError("NOT_FOUND", `No accepted ADR found for "${slug}".`, [
1005
+ `Looked for an ADR-####-${slug}.md in the ADR directory.`
1006
+ ]);
1007
+ }
1008
+ const content = await readFile3(path5.join(adrDirAbsolute, match.name), "utf8");
1009
+ if (!/(##\s+Status\r?\n\r?\n)Accepted\b/u.test(content)) {
1010
+ throw new AdrSupersedeError(
1011
+ "NOT_ACCEPTED",
1012
+ `ADR ${match.name} is not Accepted, so there is nothing to supersede.`,
1013
+ ["Only an accepted decision can be superseded. Accept it first with `recall adr accept`."]
1014
+ );
1015
+ }
1016
+ return { fileName: match.name, content };
1017
+ }
1018
+ async function write2(options, relativePath, content, force) {
1019
+ const plan = createWritePlan({
1020
+ rootDir: options.rootDir,
1021
+ files: [{ path: relativePath, content }],
1022
+ force
1023
+ });
1024
+ if (plan.hasErrors) {
1025
+ throw new AdrSupersedeError(
1026
+ "WRITE_PLAN_ERROR",
1027
+ "ADR supersede write plan contains errors.",
1028
+ plan.entries.filter(
1029
+ (entry) => entry.action === "error"
1030
+ ).map((entry) => `${entry.path}: ${entry.reason}`)
1031
+ );
1032
+ }
1033
+ return executeWritePlan(plan, { dryRun: options.dryRun });
1034
+ }
1035
+ function formatAdrSupersedeResult(result) {
1036
+ const lines = [
1037
+ result.dryRun ? "Recall OS ADR supersede dry run complete." : "Recall OS ADR supersede complete.",
1038
+ `Superseded: ${result.oldPath} (now marked superseded by ${result.newRef})`,
1039
+ `New decision: ${result.newPath}`
1040
+ ];
1041
+ appendWriteSummary(lines, { dryRun: result.dryRun, writeResult: result.writeResult });
1042
+ if (!result.dryRun) {
1043
+ appendNextSteps(lines, [
1044
+ `Fill ${result.newPath}: Context (what changed), Decision, Alternatives, Consequences.`,
1045
+ `${result.oldRef} stays in history as superseded; update any memory that still relies on it.`,
1046
+ "Run `recall doctor` \u2014 it flags memory that still references the superseded decision."
1047
+ ]);
1048
+ }
1049
+ return `${lines.join("\n")}
1050
+ `;
1051
+ }
1052
+ function createSlug2(name, field) {
1053
+ const withoutPrefix = name.replace(/^ADR-(?:PROPOSED-|\d{4,}-)/iu, "");
1054
+ try {
1055
+ return slugify(withoutPrefix);
1056
+ } catch (error) {
1057
+ if (error instanceof SlugifyError) {
1058
+ throw new AdrSupersedeError("INVALID_ADR_NAME", `Invalid ${field}: ${error.message}`);
1059
+ }
1060
+ throw error;
1061
+ }
1062
+ }
1063
+ function escapeRegExp2(value) {
1064
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
1065
+ }
1066
+ async function loadRequiredConfig3(rootDir) {
1067
+ try {
1068
+ return await loadConfig(rootDir);
1069
+ } catch (error) {
1070
+ if (error instanceof ConfigLoadError || error instanceof ConfigValidationError) {
1071
+ throw new AdrSupersedeError(
1072
+ "CONFIG_REQUIRED",
1073
+ "Recall OS config not found or invalid. Run `recall init` first.",
1074
+ [error.message]
1075
+ );
1076
+ }
1077
+ throw error;
1078
+ }
1079
+ }
1080
+
1081
+ // src/core/generator/generate-feature.ts
1082
+ import path6 from "path";
895
1083
  var featureTemplates = [
896
1084
  {
897
1085
  fileName: "PRD.md",
@@ -1043,7 +1231,7 @@ Pending.
1043
1231
  ];
1044
1232
  function generateFeatureFiles(options) {
1045
1233
  const slug = slugify(options.featureName);
1046
- const featureDir = path5.posix.join(options.featuresDir, `${options.featureId}-${slug}`);
1234
+ const featureDir = path6.posix.join(options.featuresDir, `${options.featureId}-${slug}`);
1047
1235
  const title = titleizeFeatureName(options.featureName);
1048
1236
  const context = createTemplateContext({
1049
1237
  featureId: options.featureId,
@@ -1051,7 +1239,7 @@ function generateFeatureFiles(options) {
1051
1239
  title
1052
1240
  });
1053
1241
  return featureTemplates.map((template) => ({
1054
- path: path5.posix.join(featureDir, template.fileName),
1242
+ path: path6.posix.join(featureDir, template.fileName),
1055
1243
  content: renderTemplate(template.content, context)
1056
1244
  }));
1057
1245
  }
@@ -1060,7 +1248,7 @@ function titleizeFeatureName(featureName) {
1060
1248
  }
1061
1249
 
1062
1250
  // src/core/naming/feature-number.ts
1063
- import { readdir as readdir3 } from "fs/promises";
1251
+ import { readdir as readdir4 } from "fs/promises";
1064
1252
  var FEATURE_FOLDER_PATTERN = /^F-(\d{3,})-([a-z0-9]+(?:-[a-z0-9]+)*)$/u;
1065
1253
  async function getFeatureFolderForSlug(featuresDirAbsolutePath, slug) {
1066
1254
  const existingFolders = await readExistingFeatureFolders(featuresDirAbsolutePath);
@@ -1093,7 +1281,7 @@ function formatFeatureNumber(featureNumber) {
1093
1281
  async function readExistingFeatureFolders(featuresDirAbsolutePath) {
1094
1282
  let entries;
1095
1283
  try {
1096
- entries = await readdir3(featuresDirAbsolutePath, { withFileTypes: true });
1284
+ entries = await readdir4(featuresDirAbsolutePath, { withFileTypes: true });
1097
1285
  } catch (error) {
1098
1286
  const nodeError = error;
1099
1287
  if (nodeError.code === "ENOENT") {
@@ -1132,7 +1320,7 @@ var FeatureCreateError = class extends Error {
1132
1320
  };
1133
1321
  async function createFeature(options) {
1134
1322
  const slug = createFeatureSlug(options.name);
1135
- const config = await loadRequiredConfig3(options.rootDir);
1323
+ const config = await loadRequiredConfig4(options.rootDir);
1136
1324
  const featuresDirPath = resolveSafePath(options.rootDir, config.featuresDir);
1137
1325
  const featureFolder = await getFeatureFolderForSlug(featuresDirPath.absolutePath, slug);
1138
1326
  const files = generateFeatureFiles({
@@ -1191,7 +1379,7 @@ function createFeatureSlug(name) {
1191
1379
  throw error;
1192
1380
  }
1193
1381
  }
1194
- async function loadRequiredConfig3(rootDir) {
1382
+ async function loadRequiredConfig4(rootDir) {
1195
1383
  try {
1196
1384
  return await loadConfig(rootDir);
1197
1385
  } catch (error) {
@@ -1232,8 +1420,8 @@ function createDefaultConfig(overrides = {}) {
1232
1420
 
1233
1421
  // src/core/adopt/inspect-repo.ts
1234
1422
  import { existsSync as existsSync3 } from "fs";
1235
- import { readFile as readFile3, readdir as readdir4 } from "fs/promises";
1236
- import path6 from "path";
1423
+ import { readFile as readFile4, readdir as readdir5 } from "fs/promises";
1424
+ import path7 from "path";
1237
1425
  var FRAMEWORK_SOURCES = {
1238
1426
  "Next.js": "package.json",
1239
1427
  React: "package.json",
@@ -1256,7 +1444,7 @@ var FRAMEWORK_SOURCES = {
1256
1444
  Flutter: "pubspec.yaml"
1257
1445
  };
1258
1446
  async function inspectRepo(rootDir) {
1259
- const has = (relativePath) => existsSync3(path6.join(rootDir, relativePath));
1447
+ const has = (relativePath) => existsSync3(path7.join(rootDir, relativePath));
1260
1448
  const languages = /* @__PURE__ */ new Set();
1261
1449
  const frameworks = /* @__PURE__ */ new Set();
1262
1450
  const pkg = has("package.json") ? await readJson(rootDir, "package.json") : null;
@@ -1470,7 +1658,7 @@ async function findTestFile(rootDir) {
1470
1658
  }
1471
1659
  let entries;
1472
1660
  try {
1473
- entries = await readdir4(dir, { withFileTypes: true });
1661
+ entries = await readdir5(dir, { withFileTypes: true });
1474
1662
  } catch {
1475
1663
  continue;
1476
1664
  }
@@ -1481,10 +1669,10 @@ async function findTestFile(rootDir) {
1481
1669
  }
1482
1670
  if (entry.isDirectory()) {
1483
1671
  if (!TEST_WALK_SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
1484
- stack.push(path6.join(dir, entry.name));
1672
+ stack.push(path7.join(dir, entry.name));
1485
1673
  }
1486
1674
  } else if (TEST_FILE_PATTERNS.some((pattern) => pattern.test(entry.name))) {
1487
- return path6.relative(rootDir, path6.join(dir, entry.name));
1675
+ return path7.relative(rootDir, path7.join(dir, entry.name));
1488
1676
  }
1489
1677
  }
1490
1678
  }
@@ -1508,7 +1696,7 @@ async function readJson(rootDir, relativePath) {
1508
1696
  }
1509
1697
  async function readText(rootDir, relativePath) {
1510
1698
  try {
1511
- return await readFile3(path6.join(rootDir, relativePath), "utf8");
1699
+ return await readFile4(path7.join(rootDir, relativePath), "utf8");
1512
1700
  } catch {
1513
1701
  return "";
1514
1702
  }
@@ -1684,8 +1872,8 @@ function formatList2(values) {
1684
1872
 
1685
1873
  // src/core/doctor/checks/code-reference-check.ts
1686
1874
  import { existsSync as existsSync4 } from "fs";
1687
- import { readFile as readFile4, readdir as readdir5 } from "fs/promises";
1688
- import path7 from "path";
1875
+ import { readFile as readFile5, readdir as readdir6 } from "fs/promises";
1876
+ import path8 from "path";
1689
1877
  var featureFolderPattern = /^F-\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1690
1878
  var FEATURE_DOCS = ["PRD.md", "ARCHITECTURE_IMPACT.md"];
1691
1879
  var MODULE_DOCS = ["MODULE.md", "DECISIONS.md"];
@@ -1702,7 +1890,7 @@ async function checkCodeReferences(context) {
1702
1890
  continue;
1703
1891
  }
1704
1892
  for (const doc of FEATURE_DOCS) {
1705
- const relativePath = path7.posix.join(context.config.featuresDir, folder.name, doc);
1893
+ const relativePath = path8.posix.join(context.config.featuresDir, folder.name, doc);
1706
1894
  findings.push(...await checkDoc(context.rootDir, relativePath));
1707
1895
  }
1708
1896
  }
@@ -1712,7 +1900,7 @@ async function checkCodeReferences(context) {
1712
1900
  continue;
1713
1901
  }
1714
1902
  for (const doc of MODULE_DOCS) {
1715
- const relativePath = path7.posix.join(context.config.modulesDir, folder.name, doc);
1903
+ const relativePath = path8.posix.join(context.config.modulesDir, folder.name, doc);
1716
1904
  findings.push(...await checkDoc(context.rootDir, relativePath));
1717
1905
  }
1718
1906
  }
@@ -1731,7 +1919,7 @@ async function checkDoc(rootDir, relativePath) {
1731
1919
  continue;
1732
1920
  }
1733
1921
  seen.add(reference);
1734
- if (!existsSync4(path7.join(rootDir, reference))) {
1922
+ if (!existsSync4(path8.join(rootDir, reference))) {
1735
1923
  findings.push({
1736
1924
  severity: "warning",
1737
1925
  check: "drift-code-reference",
@@ -1744,7 +1932,7 @@ async function checkDoc(rootDir, relativePath) {
1744
1932
  }
1745
1933
  async function readDirIfExists(rootDir, relativePath) {
1746
1934
  try {
1747
- return await readdir5(path7.join(rootDir, relativePath), { withFileTypes: true });
1935
+ return await readdir6(path8.join(rootDir, relativePath), { withFileTypes: true });
1748
1936
  } catch (error) {
1749
1937
  const nodeError = error;
1750
1938
  if (nodeError.code === "ENOENT") {
@@ -1755,7 +1943,7 @@ async function readDirIfExists(rootDir, relativePath) {
1755
1943
  }
1756
1944
  async function readFileIfExists(rootDir, relativePath) {
1757
1945
  try {
1758
- return await readFile4(path7.join(rootDir, relativePath), "utf8");
1946
+ return await readFile5(path8.join(rootDir, relativePath), "utf8");
1759
1947
  } catch (error) {
1760
1948
  const nodeError = error;
1761
1949
  if (nodeError.code === "ENOENT") {
@@ -1766,12 +1954,12 @@ async function readFileIfExists(rootDir, relativePath) {
1766
1954
  }
1767
1955
 
1768
1956
  // src/core/doctor/checks/config-check.ts
1769
- import { readFile as readFile5 } from "fs/promises";
1957
+ import { readFile as readFile6 } from "fs/promises";
1770
1958
  async function checkConfig(rootDir) {
1771
1959
  const configPath = resolveSafePath(rootDir, CONFIG_PATH);
1772
1960
  let rawConfig;
1773
1961
  try {
1774
- rawConfig = await readFile5(configPath.absolutePath, "utf8");
1962
+ rawConfig = await readFile6(configPath.absolutePath, "utf8");
1775
1963
  } catch (error) {
1776
1964
  const nodeError = error;
1777
1965
  if (nodeError.code === "ENOENT") {
@@ -1834,8 +2022,8 @@ async function checkConfig(rootDir) {
1834
2022
  }
1835
2023
 
1836
2024
  // src/core/doctor/checks/content-check.ts
1837
- import { readFile as readFile6, readdir as readdir6 } from "fs/promises";
1838
- import path8 from "path";
2025
+ import { readFile as readFile7, readdir as readdir7 } from "fs/promises";
2026
+ import path9 from "path";
1839
2027
  var featureFolderPattern2 = /^F-\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
1840
2028
  var acceptedAdrPattern = /^ADR-\d{4,}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/u;
1841
2029
  var SECURITY_MODEL_PATH = "docs/20-security/SECURITY_MODEL.md";
@@ -1850,7 +2038,7 @@ async function checkContent(context) {
1850
2038
  (entry) => entry.isDirectory() && featureFolderPattern2.test(entry.name)
1851
2039
  );
1852
2040
  for (const folder of featureFolders) {
1853
- const prdPath = path8.posix.join(context.config.featuresDir, folder.name, "PRD.md");
2041
+ const prdPath = path9.posix.join(context.config.featuresDir, folder.name, "PRD.md");
1854
2042
  const prd = await readFileIfExists2(context.rootDir, prdPath);
1855
2043
  if (prd === void 0) {
1856
2044
  continue;
@@ -1883,7 +2071,7 @@ async function checkContent(context) {
1883
2071
  findings.push(...await checkSecurityDoc(context.rootDir));
1884
2072
  }
1885
2073
  for (const folder of moduleFolders) {
1886
- const modulePath = path8.posix.join(context.config.modulesDir, folder.name, "MODULE.md");
2074
+ const modulePath = path9.posix.join(context.config.modulesDir, folder.name, "MODULE.md");
1887
2075
  const moduleDoc = await readFileIfExists2(context.rootDir, modulePath);
1888
2076
  if (moduleDoc === void 0) {
1889
2077
  continue;
@@ -1961,7 +2149,7 @@ function getSection(content, heading) {
1961
2149
  }
1962
2150
  async function readDirIfExists2(rootDir, relativePath) {
1963
2151
  try {
1964
- return await readdir6(path8.join(rootDir, relativePath), { withFileTypes: true });
2152
+ return await readdir7(path9.join(rootDir, relativePath), { withFileTypes: true });
1965
2153
  } catch (error) {
1966
2154
  const nodeError = error;
1967
2155
  if (nodeError.code === "ENOENT") {
@@ -1972,7 +2160,7 @@ async function readDirIfExists2(rootDir, relativePath) {
1972
2160
  }
1973
2161
  async function readFileIfExists2(rootDir, relativePath) {
1974
2162
  try {
1975
- return await readFile6(path8.join(rootDir, relativePath), "utf8");
2163
+ return await readFile7(path9.join(rootDir, relativePath), "utf8");
1976
2164
  } catch (error) {
1977
2165
  const nodeError = error;
1978
2166
  if (nodeError.code === "ENOENT") {
@@ -1983,8 +2171,8 @@ async function readFileIfExists2(rootDir, relativePath) {
1983
2171
  }
1984
2172
 
1985
2173
  // src/core/doctor/checks/drift-check.ts
1986
- import { readFile as readFile7, readdir as readdir7 } from "fs/promises";
1987
- import path9 from "path";
2174
+ import { readFile as readFile8, readdir as readdir8 } from "fs/promises";
2175
+ import path10 from "path";
1988
2176
  var adrFilePattern = /^ADR-(\d{4,})-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/iu;
1989
2177
  var adrReferencePattern = /ADR-\d{4,}/giu;
1990
2178
  async function checkDrift(context) {
@@ -2001,12 +2189,12 @@ async function loadKnownAdrs(rootDir, adrDir) {
2001
2189
  const known = /* @__PURE__ */ new Map();
2002
2190
  const files = await readMarkdownFiles(rootDir, adrDir);
2003
2191
  for (const file of files) {
2004
- const match = adrFilePattern.exec(path9.basename(file));
2192
+ const match = adrFilePattern.exec(path10.basename(file));
2005
2193
  if (match === null) {
2006
2194
  continue;
2007
2195
  }
2008
2196
  const id = `ADR-${match[1]}`;
2009
- const content = await readFile7(path9.join(rootDir, file), "utf8");
2197
+ const content = await readFile8(path10.join(rootDir, file), "utf8");
2010
2198
  const accepted = sectionContains(content, "Status", /\baccepted\b/iu);
2011
2199
  const existing = known.get(id);
2012
2200
  if (existing === void 0 || !existing.accepted && accepted) {
@@ -2019,7 +2207,7 @@ async function checkReferences(rootDir, referenceDir, knownAdrs) {
2019
2207
  const findings = [];
2020
2208
  const files = await readMarkdownFiles(rootDir, referenceDir);
2021
2209
  for (const file of files) {
2022
- const content = await readFile7(path9.join(rootDir, file), "utf8");
2210
+ const content = await readFile8(path10.join(rootDir, file), "utf8");
2023
2211
  const referenced = /* @__PURE__ */ new Set();
2024
2212
  for (const match of stripCode(content).matchAll(adrReferencePattern)) {
2025
2213
  referenced.add(match[0].toUpperCase());
@@ -2054,7 +2242,7 @@ async function readMarkdownFiles(rootDir, relativeDir) {
2054
2242
  const entries = await readDirIfExists3(rootDir, relativeDir);
2055
2243
  const files = [];
2056
2244
  for (const entry of entries) {
2057
- const childRelative = path9.posix.join(relativeDir, entry.name);
2245
+ const childRelative = path10.posix.join(relativeDir, entry.name);
2058
2246
  if (entry.isDirectory()) {
2059
2247
  files.push(...await readMarkdownFiles(rootDir, childRelative));
2060
2248
  continue;
@@ -2087,7 +2275,7 @@ function getSection2(content, heading) {
2087
2275
  }
2088
2276
  async function readDirIfExists3(rootDir, relativePath) {
2089
2277
  try {
2090
- return await readdir7(path9.join(rootDir, relativePath), { withFileTypes: true });
2278
+ return await readdir8(path10.join(rootDir, relativePath), { withFileTypes: true });
2091
2279
  } catch (error) {
2092
2280
  const nodeError = error;
2093
2281
  if (nodeError.code === "ENOENT") {
@@ -2098,8 +2286,8 @@ async function readDirIfExists3(rootDir, relativePath) {
2098
2286
  }
2099
2287
 
2100
2288
  // src/core/doctor/checks/memory-integrity-check.ts
2101
- import { lstat as lstat2, readFile as readFile8, readdir as readdir8 } from "fs/promises";
2102
- import path10 from "path";
2289
+ import { lstat as lstat2, readFile as readFile9, readdir as readdir9 } from "fs/promises";
2290
+ import path11 from "path";
2103
2291
 
2104
2292
  // src/core/adr/adr-sections.ts
2105
2293
  var REQUIRED_ADR_SECTIONS = [
@@ -2163,7 +2351,7 @@ async function checkFeatureFolders(rootDir, featuresDir) {
2163
2351
  );
2164
2352
  for (const featureFolder of featureFolders) {
2165
2353
  for (const requiredDoc of requiredFeatureDocs) {
2166
- const filePath = path10.posix.join(featuresDir, featureFolder.name, requiredDoc);
2354
+ const filePath = path11.posix.join(featuresDir, featureFolder.name, requiredDoc);
2167
2355
  if (!await isFile(rootDir, filePath)) {
2168
2356
  findings.push({
2169
2357
  severity: "error",
@@ -2187,7 +2375,7 @@ async function checkModuleFolders(rootDir, modulesDir) {
2187
2375
  const moduleFolders = entries.filter((entry) => entry.isDirectory());
2188
2376
  for (const moduleFolder of moduleFolders) {
2189
2377
  for (const requiredDoc of requiredModuleDocs) {
2190
- const filePath = path10.posix.join(modulesDir, moduleFolder.name, requiredDoc);
2378
+ const filePath = path11.posix.join(modulesDir, moduleFolder.name, requiredDoc);
2191
2379
  if (!await isFile(rootDir, filePath)) {
2192
2380
  findings.push({
2193
2381
  severity: "error",
@@ -2210,8 +2398,8 @@ async function checkAdrFiles(rootDir, adrDir) {
2210
2398
  const entries = await readDirIfExists4(rootDir, adrDir);
2211
2399
  const adrFiles = entries.filter((entry) => entry.isFile() && adrFilePattern2.test(entry.name));
2212
2400
  for (const adrFile of adrFiles) {
2213
- const filePath = path10.posix.join(adrDir, adrFile.name);
2214
- const content = await readFile8(path10.join(rootDir, filePath), "utf8");
2401
+ const filePath = path11.posix.join(adrDir, adrFile.name);
2402
+ const content = await readFile9(path11.join(rootDir, filePath), "utf8");
2215
2403
  for (const requiredSection of requiredAdrSections) {
2216
2404
  if (!content.includes(requiredSection)) {
2217
2405
  findings.push({
@@ -2232,7 +2420,7 @@ async function checkAdrFiles(rootDir, adrDir) {
2232
2420
  }
2233
2421
  async function readDirIfExists4(rootDir, relativePath) {
2234
2422
  try {
2235
- return await readdir8(path10.join(rootDir, relativePath), { withFileTypes: true });
2423
+ return await readdir9(path11.join(rootDir, relativePath), { withFileTypes: true });
2236
2424
  } catch (error) {
2237
2425
  const nodeError = error;
2238
2426
  if (nodeError.code === "ENOENT") {
@@ -2243,7 +2431,7 @@ async function readDirIfExists4(rootDir, relativePath) {
2243
2431
  }
2244
2432
  async function isFile(rootDir, relativePath) {
2245
2433
  try {
2246
- return (await lstat2(path10.join(rootDir, relativePath))).isFile();
2434
+ return (await lstat2(path11.join(rootDir, relativePath))).isFile();
2247
2435
  } catch (error) {
2248
2436
  const nodeError = error;
2249
2437
  if (nodeError.code === "ENOENT") {
@@ -2255,7 +2443,7 @@ async function isFile(rootDir, relativePath) {
2255
2443
 
2256
2444
  // src/core/doctor/checks/required-files-check.ts
2257
2445
  import { lstat as lstat3 } from "fs/promises";
2258
- import path11 from "path";
2446
+ import path12 from "path";
2259
2447
  var rootFiles = ["AGENTS.md", "CLAUDE.md"];
2260
2448
  var requiredDocs = [
2261
2449
  "00-product/PRD.md",
@@ -2282,12 +2470,12 @@ async function checkRequiredFiles(context) {
2282
2470
  }
2283
2471
  }
2284
2472
  for (const relativeDocPath of requiredDocs) {
2285
- const filePath = path11.posix.join(docsDir, relativeDocPath);
2473
+ const filePath = path12.posix.join(docsDir, relativeDocPath);
2286
2474
  if (!await isFile2(context.rootDir, filePath)) {
2287
2475
  findings.push(missingFile(filePath, "required-docs"));
2288
2476
  }
2289
2477
  }
2290
- const adrIndexPath = path11.posix.join(context.config?.adrDir ?? "docs/adrs", "README.md");
2478
+ const adrIndexPath = path12.posix.join(context.config?.adrDir ?? "docs/adrs", "README.md");
2291
2479
  if (!await isFile2(context.rootDir, adrIndexPath)) {
2292
2480
  findings.push(missingFile(adrIndexPath, "required-docs"));
2293
2481
  }
@@ -2313,7 +2501,7 @@ async function checkRequiredFiles(context) {
2313
2501
  }
2314
2502
  async function isFile2(rootDir, relativePath) {
2315
2503
  try {
2316
- return (await lstat3(path11.join(rootDir, relativePath))).isFile();
2504
+ return (await lstat3(path12.join(rootDir, relativePath))).isFile();
2317
2505
  } catch (error) {
2318
2506
  const nodeError = error;
2319
2507
  if (nodeError.code === "ENOENT") {
@@ -2324,7 +2512,7 @@ async function isFile2(rootDir, relativePath) {
2324
2512
  }
2325
2513
  async function isDirectory(rootDir, relativePath) {
2326
2514
  try {
2327
- return (await lstat3(path11.join(rootDir, relativePath))).isDirectory();
2515
+ return (await lstat3(path12.join(rootDir, relativePath))).isDirectory();
2328
2516
  } catch (error) {
2329
2517
  const nodeError = error;
2330
2518
  if (nodeError.code === "ENOENT") {
@@ -2343,8 +2531,8 @@ function missingFile(pathValue, check) {
2343
2531
  }
2344
2532
 
2345
2533
  // src/core/doctor/checks/standards-check.ts
2346
- import { lstat as lstat4, readFile as readFile9, readdir as readdir9 } from "fs/promises";
2347
- import path12 from "path";
2534
+ import { lstat as lstat4, readFile as readFile10, readdir as readdir10 } from "fs/promises";
2535
+ import path13 from "path";
2348
2536
  var featureFolderPattern4 = /^F-\d{3,}-[a-z0-9]+(?:-[a-z0-9]+)*$/u;
2349
2537
  var adrFilePattern3 = /^ADR-\d{4,}-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/u;
2350
2538
  var securitySensitivePattern = /\b(auth|authentication|authorization|secrets?|storage|networking?|telemetry|file writes?|write policy|dependencies?|mcp|ai api|cloud|runtime)\b/iu;
@@ -2364,10 +2552,10 @@ async function checkFeatureStandards(rootDir, featuresDir) {
2364
2552
  (entry) => entry.isDirectory() && featureFolderPattern4.test(entry.name)
2365
2553
  );
2366
2554
  for (const featureFolder of featureFolders) {
2367
- const featureDir = path12.posix.join(featuresDir, featureFolder.name);
2368
- const completionReportPath = path12.posix.join(featureDir, "COMPLETION_REPORT.md");
2369
- const reviewPath = path12.posix.join(featureDir, "REVIEW.md");
2370
- const architectureImpactPath = path12.posix.join(featureDir, "ARCHITECTURE_IMPACT.md");
2555
+ const featureDir = path13.posix.join(featuresDir, featureFolder.name);
2556
+ const completionReportPath = path13.posix.join(featureDir, "COMPLETION_REPORT.md");
2557
+ const reviewPath = path13.posix.join(featureDir, "REVIEW.md");
2558
+ const architectureImpactPath = path13.posix.join(featureDir, "ARCHITECTURE_IMPACT.md");
2371
2559
  const completionReport = await readFileIfExists3(rootDir, completionReportPath);
2372
2560
  const review = await readFileIfExists3(rootDir, reviewPath);
2373
2561
  const architectureImpact = await readFileIfExists3(rootDir, architectureImpactPath);
@@ -2417,8 +2605,8 @@ async function checkAdrStandards(rootDir, adrDir) {
2417
2605
  const entries = await readDirIfExists5(rootDir, adrDir);
2418
2606
  const adrFiles = entries.filter((entry) => entry.isFile() && adrFilePattern3.test(entry.name));
2419
2607
  for (const adrFile of adrFiles) {
2420
- const adrPath = path12.posix.join(adrDir, adrFile.name);
2421
- const content = await readFile9(path12.join(rootDir, adrPath), "utf8");
2608
+ const adrPath = path13.posix.join(adrDir, adrFile.name);
2609
+ const content = await readFile10(path13.join(rootDir, adrPath), "utf8");
2422
2610
  const isAccepted = sectionContains2(content, "Status", /\baccepted\b/iu);
2423
2611
  if (!hasMeaningfulSection(content, "Consequences")) {
2424
2612
  findings.push({
@@ -2502,7 +2690,7 @@ function isPlaceholder(value) {
2502
2690
  }
2503
2691
  async function readDirIfExists5(rootDir, relativePath) {
2504
2692
  try {
2505
- return await readdir9(path12.join(rootDir, relativePath), { withFileTypes: true });
2693
+ return await readdir10(path13.join(rootDir, relativePath), { withFileTypes: true });
2506
2694
  } catch (error) {
2507
2695
  const nodeError = error;
2508
2696
  if (nodeError.code === "ENOENT") {
@@ -2516,7 +2704,7 @@ async function readFileIfExists3(rootDir, relativePath) {
2516
2704
  if (!await isFile3(rootDir, relativePath)) {
2517
2705
  return void 0;
2518
2706
  }
2519
- return await readFile9(path12.join(rootDir, relativePath), "utf8");
2707
+ return await readFile10(path13.join(rootDir, relativePath), "utf8");
2520
2708
  } catch (error) {
2521
2709
  const nodeError = error;
2522
2710
  if (nodeError.code === "ENOENT") {
@@ -2527,7 +2715,7 @@ async function readFileIfExists3(rootDir, relativePath) {
2527
2715
  }
2528
2716
  async function isFile3(rootDir, relativePath) {
2529
2717
  try {
2530
- return (await lstat4(path12.join(rootDir, relativePath))).isFile();
2718
+ return (await lstat4(path13.join(rootDir, relativePath))).isFile();
2531
2719
  } catch (error) {
2532
2720
  const nodeError = error;
2533
2721
  if (nodeError.code === "ENOENT") {
@@ -2537,6 +2725,110 @@ async function isFile3(rootDir, relativePath) {
2537
2725
  }
2538
2726
  }
2539
2727
 
2728
+ // src/core/doctor/checks/superseded-check.ts
2729
+ import { readFile as readFile11, readdir as readdir11 } from "fs/promises";
2730
+ import path14 from "path";
2731
+ var adrFilePattern4 = /^ADR-(\d{4,})-[a-z0-9]+(?:-[a-z0-9]+)*\.md$/iu;
2732
+ var adrReferencePattern2 = /ADR-\d{4,}/giu;
2733
+ async function checkSuperseded(context) {
2734
+ if (context.config === void 0) {
2735
+ return [];
2736
+ }
2737
+ const supersededIds = await loadSupersededAdrIds(context.rootDir, context.config.adrDir);
2738
+ if (supersededIds.size === 0) {
2739
+ return [];
2740
+ }
2741
+ const findings = [];
2742
+ findings.push(
2743
+ ...await checkReferences2(context.rootDir, context.config.featuresDir, supersededIds)
2744
+ );
2745
+ findings.push(
2746
+ ...await checkReferences2(context.rootDir, context.config.modulesDir, supersededIds)
2747
+ );
2748
+ return findings;
2749
+ }
2750
+ async function loadSupersededAdrIds(rootDir, adrDir) {
2751
+ const superseded = /* @__PURE__ */ new Set();
2752
+ const files = await readMarkdownFiles2(rootDir, adrDir);
2753
+ for (const file of files) {
2754
+ const match = adrFilePattern4.exec(path14.basename(file));
2755
+ if (match === null) {
2756
+ continue;
2757
+ }
2758
+ const content = await readFile11(path14.join(rootDir, file), "utf8");
2759
+ if (statusContains(content, /superseded\s+by/iu)) {
2760
+ superseded.add(`ADR-${match[1]}`.toUpperCase());
2761
+ }
2762
+ }
2763
+ return superseded;
2764
+ }
2765
+ async function checkReferences2(rootDir, referenceDir, supersededIds) {
2766
+ const findings = [];
2767
+ const files = await readMarkdownFiles2(rootDir, referenceDir);
2768
+ for (const file of files) {
2769
+ const content = await readFile11(path14.join(rootDir, file), "utf8");
2770
+ const referenced = /* @__PURE__ */ new Set();
2771
+ for (const match of stripCode2(content).matchAll(adrReferencePattern2)) {
2772
+ referenced.add(match[0].toUpperCase());
2773
+ }
2774
+ for (const id of referenced) {
2775
+ if (supersededIds.has(id)) {
2776
+ findings.push({
2777
+ severity: "warning",
2778
+ check: "superseded-reference",
2779
+ message: `Repository memory references ${id}, which has been superseded \u2014 update it to the current decision.`,
2780
+ path: file
2781
+ });
2782
+ }
2783
+ }
2784
+ }
2785
+ return findings;
2786
+ }
2787
+ function statusContains(content, pattern) {
2788
+ const lines = content.split(/\r?\n/u);
2789
+ const startIndex = lines.findIndex((line) => line.trim().toLowerCase() === "## status");
2790
+ if (startIndex === -1) {
2791
+ return false;
2792
+ }
2793
+ const body = [];
2794
+ for (let index = startIndex + 1; index < lines.length; index += 1) {
2795
+ if (/^##\s+/u.test(lines[index])) {
2796
+ break;
2797
+ }
2798
+ body.push(lines[index]);
2799
+ }
2800
+ return pattern.test(body.join("\n"));
2801
+ }
2802
+ function stripCode2(content) {
2803
+ return content.replace(/```[\s\S]*?```/gu, " ").replace(/~~~[\s\S]*?~~~/gu, " ").replace(/`[^`]*`/gu, " ");
2804
+ }
2805
+ async function readMarkdownFiles2(rootDir, relativeDir) {
2806
+ const entries = await readDirIfExists6(rootDir, relativeDir);
2807
+ const files = [];
2808
+ for (const entry of entries) {
2809
+ const childRelative = path14.posix.join(relativeDir, entry.name);
2810
+ if (entry.isDirectory()) {
2811
+ files.push(...await readMarkdownFiles2(rootDir, childRelative));
2812
+ continue;
2813
+ }
2814
+ if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
2815
+ files.push(childRelative);
2816
+ }
2817
+ }
2818
+ return files;
2819
+ }
2820
+ async function readDirIfExists6(rootDir, relativePath) {
2821
+ try {
2822
+ return await readdir11(path14.join(rootDir, relativePath), { withFileTypes: true });
2823
+ } catch (error) {
2824
+ const nodeError = error;
2825
+ if (nodeError.code === "ENOENT") {
2826
+ return [];
2827
+ }
2828
+ throw error;
2829
+ }
2830
+ }
2831
+
2540
2832
  // src/core/doctor/doctor-check.ts
2541
2833
  async function runDoctor(rootDir) {
2542
2834
  const findings = [];
@@ -2553,6 +2845,7 @@ async function runDoctor(rootDir) {
2553
2845
  findings.push(...await checkDrift(context));
2554
2846
  findings.push(...await checkContent(context));
2555
2847
  findings.push(...await checkCodeReferences(context));
2848
+ findings.push(...await checkSuperseded(context));
2556
2849
  }
2557
2850
  return createDoctorReport(findings);
2558
2851
  }
@@ -2630,10 +2923,10 @@ function formatDoctorResult(result) {
2630
2923
 
2631
2924
  // src/commands/init.ts
2632
2925
  import { existsSync as existsSync6 } from "fs";
2633
- import path15 from "path";
2926
+ import path17 from "path";
2634
2927
 
2635
2928
  // src/core/generator/generate-init.ts
2636
- import path13 from "path";
2929
+ import path15 from "path";
2637
2930
  var neutralTemplates = [
2638
2931
  {
2639
2932
  path: "AGENTS.md",
@@ -2652,6 +2945,32 @@ Required reading:
2652
2945
  - \`docs/60-engineering/ENGINEERING_STANDARDS.md\`
2653
2946
 
2654
2947
  Repository rules override model preferences. If instructions conflict, stop and report the conflict.
2948
+
2949
+ ## Recall OS commands
2950
+
2951
+ This repository is maintained with the Recall OS CLI. Use these commands yourself as you work \u2014 do not
2952
+ ask the human to run them, and do not search the web for them (this is a project-local tool):
2953
+
2954
+ - \`recall doctor\` \u2014 validate repository memory; run it before claiming any work is complete.
2955
+ - \`recall feature create <name>\` \u2014 scaffold feature memory before non-trivial feature work.
2956
+ - \`recall adr create <title>\` \u2014 propose a decision; \`recall adr accept <name>\` accepts it.
2957
+ - \`recall adr supersede <old> <new-title>\` \u2014 record a changed decision (never overwrite an accepted ADR).
2958
+ - \`recall module create <name>\` \u2014 scaffold module memory for a new responsibility boundary.
2959
+ - \`recall mcp add <server>\` \u2014 capture an MCP tool's context into memory, offline.
2960
+
2961
+ Full command reference: \`docs/ai/RECALL_COMMANDS.md\`.
2962
+
2963
+ ## Changing an accepted decision
2964
+
2965
+ Before changing anything an accepted ADR governs (framework, database, auth, API shape, and similar):
2966
+
2967
+ 1. Check \`docs/adrs/\` for an accepted ADR that covers it.
2968
+ 2. If your change contradicts one, stop and confirm with a human first \u2014 do not silently change the
2969
+ code and leave the ADR saying the opposite.
2970
+ 3. Record the change as a new decision with \`recall adr supersede <old> <new-title>\`. That supersedes
2971
+ the old ADR instead of overwriting history, so the reasoning stays auditable.
2972
+
2973
+ Repository memory is only trustworthy if decisions change through this trail, not silently.
2655
2974
  `
2656
2975
  },
2657
2976
  {
@@ -2688,10 +3007,17 @@ Before non-trivial work:
2688
3007
  - Read \`AGENTS.md\` and the docs it routes to.
2689
3008
  - Accepted decisions live in \`docs/adrs/\`; module memory lives in \`docs/30-modules/\`.
2690
3009
  - If an instruction conflicts with accepted repository memory, stop and report the conflict.
3010
+ - Before changing what an accepted ADR governs, confirm with a human and record it with
3011
+ \`recall adr supersede <old> <new-title>\` \u2014 never silently contradict an accepted decision.
2691
3012
 
2692
3013
  Source-of-truth order: accepted ADRs and repository decisions, then architecture docs, engineering
2693
3014
  standards, the current PRD, security and testing docs, module docs, feature plans, then chat history.
2694
3015
 
3016
+ Recall OS commands \u2014 use these yourself (do not web-search this project-local CLI): \`recall doctor\`,
3017
+ \`recall feature create <name>\`, \`recall adr create <title>\` then \`recall adr accept <name>\`,
3018
+ \`recall adr supersede <old> <new-title>\`, \`recall module create <name>\`, \`recall mcp add <server>\`.
3019
+ Full reference: \`docs/ai/RECALL_COMMANDS.md\`.
3020
+
2695
3021
  Before claiming work is complete, run \`recall doctor\` and fix reported errors.
2696
3022
  `
2697
3023
  },
@@ -2896,6 +3222,15 @@ Baseline rules:
2896
3222
  AI agents must follow repository memory over model preference.
2897
3223
 
2898
3224
  If a request conflicts with accepted repository memory or engineering standards, stop and report the conflict.
3225
+
3226
+ ## Changing an accepted decision
3227
+
3228
+ When work would change something an accepted ADR governs:
3229
+
3230
+ 1. Find the accepted ADR in \`docs/adrs/\` that covers it.
3231
+ 2. If the change contradicts it, stop and confirm with a human before changing the code.
3232
+ 3. Record the new decision with \`recall adr supersede <old> <new-title>\` so the old ADR is marked
3233
+ superseded and the reasoning is preserved, instead of silently editing or contradicting it.
2899
3234
  `
2900
3235
  },
2901
3236
  {
@@ -3029,6 +3364,17 @@ Options:
3029
3364
  - \`--dry-run\`: show planned writes without writing files.
3030
3365
  - \`--force\`: overwrite existing files explicitly.
3031
3366
 
3367
+ ### \`recall adr supersede <old> <new-title>\`
3368
+
3369
+ Record a changed decision. Marks an accepted ADR as \`Accepted \u2014 superseded by ADR-####\` and creates a
3370
+ new accepted ADR that declares what it supersedes, so the reasoning trail stays auditable instead of
3371
+ being overwritten. Doctor then warns about any memory still referencing the superseded decision.
3372
+
3373
+ Options:
3374
+
3375
+ - \`--dry-run\`: show planned writes without writing files.
3376
+ - \`--force\`: overwrite existing files explicitly.
3377
+
3032
3378
  ### \`recall module create <name>\`
3033
3379
 
3034
3380
  Create module memory docs under the configured modules directory.
@@ -3044,7 +3390,8 @@ Check whether repository memory is structurally healthy enough for AI-assisted w
3044
3390
  engineering evidence is present, and whether memory references decisions that exist and are accepted.
3045
3391
 
3046
3392
  Doctor also runs deterministic drift checks: feature or module memory that references a missing ADR
3047
- is an error, and memory that references a not-yet-accepted ADR is a warning.
3393
+ is an error, memory that references a not-yet-accepted ADR is a warning, and memory that still
3394
+ references a superseded decision is a warning.
3048
3395
 
3049
3396
  Exit codes:
3050
3397
 
@@ -3131,7 +3478,7 @@ jobs:
3131
3478
  }
3132
3479
  ];
3133
3480
  function generateInitFiles(options) {
3134
- const repositoryName = path13.basename(path13.resolve(options.rootDir)) || "repository";
3481
+ const repositoryName = path15.basename(path15.resolve(options.rootDir)) || "repository";
3135
3482
  const context = createTemplateContext({ repositoryName });
3136
3483
  const files = neutralTemplates.map((template) => ({
3137
3484
  path: template.path,
@@ -3158,17 +3505,17 @@ function generatePresetFiles(preset) {
3158
3505
 
3159
3506
  // src/core/hooks/detect-gates.ts
3160
3507
  import { existsSync as existsSync5 } from "fs";
3161
- import { readFile as readFile10 } from "fs/promises";
3162
- import path14 from "path";
3508
+ import { readFile as readFile12 } from "fs/promises";
3509
+ import path16 from "path";
3163
3510
  var KNOWN_SCRIPTS = ["test", "typecheck", "lint"];
3164
3511
  async function detectPreCommitGates(rootDir) {
3165
- const packageJsonPath = path14.join(rootDir, "package.json");
3512
+ const packageJsonPath = path16.join(rootDir, "package.json");
3166
3513
  if (!existsSync5(packageJsonPath)) {
3167
3514
  return [];
3168
3515
  }
3169
3516
  let scripts;
3170
3517
  try {
3171
- const raw = await readFile10(packageJsonPath, "utf8");
3518
+ const raw = await readFile12(packageJsonPath, "utf8");
3172
3519
  const parsed = JSON.parse(raw);
3173
3520
  scripts = parsed.scripts ?? {};
3174
3521
  } catch {
@@ -3183,10 +3530,10 @@ async function detectPreCommitGates(rootDir) {
3183
3530
  );
3184
3531
  }
3185
3532
  function detectPackageManager2(rootDir) {
3186
- if (existsSync5(path14.join(rootDir, "pnpm-lock.yaml"))) {
3533
+ if (existsSync5(path16.join(rootDir, "pnpm-lock.yaml"))) {
3187
3534
  return "pnpm";
3188
3535
  }
3189
- if (existsSync5(path14.join(rootDir, "yarn.lock"))) {
3536
+ if (existsSync5(path16.join(rootDir, "yarn.lock"))) {
3190
3537
  return "yarn";
3191
3538
  }
3192
3539
  return "npm";
@@ -3206,7 +3553,7 @@ function renderSessionStartHook() {
3206
3553
  adrs=$(ls docs/adrs/ADR-*.md 2>/dev/null | sed 's|.*/||;s|\\.md$||' | tr '\\n' ' ')
3207
3554
  modules=$(ls -d docs/30-modules/*/ 2>/dev/null | sed 's|docs/30-modules/||;s|/$||' | tr '\\n' ' ')
3208
3555
 
3209
- 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."
3556
+ 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."
3210
3557
 
3211
3558
  printf '{"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":"%s"}}\\n' "$context"
3212
3559
  `;
@@ -4691,8 +5038,8 @@ function parsePreset(value) {
4691
5038
  if (!result.success) {
4692
5039
  throw new PresetValidationError(
4693
5040
  result.error.issues.map((issue) => {
4694
- const path17 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
4695
- return `${path17}${issue.message}`;
5041
+ const path19 = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
5042
+ return `${path19}${issue.message}`;
4696
5043
  })
4697
5044
  );
4698
5045
  }
@@ -5381,7 +5728,7 @@ var InitError = class extends Error {
5381
5728
  }
5382
5729
  };
5383
5730
  async function initProject(options) {
5384
- if (options.force === true && options.reinit !== true && existsSync6(path15.join(options.rootDir, CONFIG_PATH))) {
5731
+ if (options.force === true && options.reinit !== true && existsSync6(path17.join(options.rootDir, CONFIG_PATH))) {
5385
5732
  throw new InitError(
5386
5733
  "EXISTING_INSTALLATION",
5387
5734
  "Refusing to re-initialize an existing Recall OS installation.",
@@ -5745,7 +6092,7 @@ async function loadConfigOrDefault2(rootDir) {
5745
6092
  }
5746
6093
 
5747
6094
  // src/core/generator/generate-module.ts
5748
- import path16 from "path";
6095
+ import path18 from "path";
5749
6096
  var moduleTemplates = [
5750
6097
  {
5751
6098
  fileName: "MODULE.md",
@@ -5818,14 +6165,14 @@ Record durable module decisions here.
5818
6165
  ];
5819
6166
  function generateModuleFiles(options) {
5820
6167
  const slug = slugify(options.moduleName);
5821
- const moduleDir = path16.posix.join(options.modulesDir, slug);
6168
+ const moduleDir = path18.posix.join(options.modulesDir, slug);
5822
6169
  const title = titleizeModuleName(options.moduleName);
5823
6170
  const context = createTemplateContext({
5824
6171
  slug,
5825
6172
  title
5826
6173
  });
5827
6174
  return moduleTemplates.map((template) => ({
5828
- path: path16.posix.join(moduleDir, template.fileName),
6175
+ path: path18.posix.join(moduleDir, template.fileName),
5829
6176
  content: renderTemplate(template.content, context)
5830
6177
  }));
5831
6178
  }
@@ -5846,7 +6193,7 @@ var ModuleCreateError = class extends Error {
5846
6193
  };
5847
6194
  async function createModule(options) {
5848
6195
  const slug = createModuleSlug(options.name);
5849
- const config = await loadRequiredConfig4(options.rootDir);
6196
+ const config = await loadRequiredConfig5(options.rootDir);
5850
6197
  const files = generateModuleFiles({
5851
6198
  modulesDir: config.modulesDir,
5852
6199
  moduleName: options.name
@@ -5900,7 +6247,7 @@ function createModuleSlug(name) {
5900
6247
  throw error;
5901
6248
  }
5902
6249
  }
5903
- async function loadRequiredConfig4(rootDir) {
6250
+ async function loadRequiredConfig5(rootDir) {
5904
6251
  try {
5905
6252
  return await loadConfig(rootDir);
5906
6253
  } catch (error) {
@@ -6074,6 +6421,20 @@ function createCliProgram(io = {}, state = { exitCode: 0 }) {
6074
6421
  });
6075
6422
  stdout.write(formatAdrAcceptResult(result));
6076
6423
  });
6424
+ adrCommand.command("supersede").description(
6425
+ "Record a changed decision: mark an accepted ADR superseded by a new accepted ADR."
6426
+ ).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(
6427
+ async (oldName, newTitle, options) => {
6428
+ const result = await supersedeAdr({
6429
+ rootDir: cwd,
6430
+ oldName,
6431
+ newTitle,
6432
+ dryRun: options.dryRun,
6433
+ force: options.force
6434
+ });
6435
+ stdout.write(formatAdrSupersedeResult(result));
6436
+ }
6437
+ );
6077
6438
  const moduleCommand = program.command("module").description("Manage Recall OS module memory.");
6078
6439
  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) => {
6079
6440
  const result = await createModule({
@@ -6167,6 +6528,15 @@ async function main(argv = process.argv.slice(2), io = {}) {
6167
6528
  `);
6168
6529
  for (const detail of error.details) {
6169
6530
  stderr.write(`- ${detail}
6531
+ `);
6532
+ }
6533
+ return 1;
6534
+ }
6535
+ if (error instanceof AdrSupersedeError) {
6536
+ stderr.write(`${error.message}
6537
+ `);
6538
+ for (const detail of error.details) {
6539
+ stderr.write(`- ${detail}
6170
6540
  `);
6171
6541
  }
6172
6542
  return 1;