@metaobjectsdev/sdk 0.5.0-rc.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 (118) hide show
  1. package/LICENSE +189 -0
  2. package/README.md +32 -0
  3. package/dist/agent-docs/body.d.ts +2 -0
  4. package/dist/agent-docs/body.d.ts.map +1 -0
  5. package/dist/agent-docs/body.js +563 -0
  6. package/dist/agent-docs/body.js.map +1 -0
  7. package/dist/agent-docs/content-hash.d.ts +8 -0
  8. package/dist/agent-docs/content-hash.d.ts.map +1 -0
  9. package/dist/agent-docs/content-hash.js +23 -0
  10. package/dist/agent-docs/content-hash.js.map +1 -0
  11. package/dist/agent-docs/index.d.ts +3 -0
  12. package/dist/agent-docs/index.d.ts.map +1 -0
  13. package/dist/agent-docs/index.js +4 -0
  14. package/dist/agent-docs/index.js.map +1 -0
  15. package/dist/config.d.ts +113 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +53 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/forge-types.d.ts +47 -0
  20. package/dist/forge-types.d.ts.map +1 -0
  21. package/dist/forge-types.js +133 -0
  22. package/dist/forge-types.js.map +1 -0
  23. package/dist/index.d.ts +20 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +39 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/memory.d.ts +30 -0
  28. package/dist/memory.d.ts.map +1 -0
  29. package/dist/memory.js +105 -0
  30. package/dist/memory.js.map +1 -0
  31. package/dist/package.d.ts +65 -0
  32. package/dist/package.d.ts.map +1 -0
  33. package/dist/package.js +105 -0
  34. package/dist/package.js.map +1 -0
  35. package/dist/paths.d.ts +5 -0
  36. package/dist/paths.d.ts.map +1 -0
  37. package/dist/paths.js +26 -0
  38. package/dist/paths.js.map +1 -0
  39. package/dist/records/any.d.ts +467 -0
  40. package/dist/records/any.d.ts.map +1 -0
  41. package/dist/records/any.js +14 -0
  42. package/dist/records/any.js.map +1 -0
  43. package/dist/records/convention.d.ts +90 -0
  44. package/dist/records/convention.d.ts.map +1 -0
  45. package/dist/records/convention.js +9 -0
  46. package/dist/records/convention.js.map +1 -0
  47. package/dist/records/core.d.ts +84 -0
  48. package/dist/records/core.d.ts.map +1 -0
  49. package/dist/records/core.js +47 -0
  50. package/dist/records/core.js.map +1 -0
  51. package/dist/records/decision.d.ts +90 -0
  52. package/dist/records/decision.d.ts.map +1 -0
  53. package/dist/records/decision.js +9 -0
  54. package/dist/records/decision.js.map +1 -0
  55. package/dist/records/failure.d.ts +93 -0
  56. package/dist/records/failure.d.ts.map +1 -0
  57. package/dist/records/failure.js +10 -0
  58. package/dist/records/failure.js.map +1 -0
  59. package/dist/records/glossary.d.ts +111 -0
  60. package/dist/records/glossary.d.ts.map +1 -0
  61. package/dist/records/glossary.js +14 -0
  62. package/dist/records/glossary.js.map +1 -0
  63. package/dist/records/principle.d.ts +99 -0
  64. package/dist/records/principle.d.ts.map +1 -0
  65. package/dist/records/principle.js +12 -0
  66. package/dist/records/principle.js.map +1 -0
  67. package/dist/storage/errors.d.ts +14 -0
  68. package/dist/storage/errors.d.ts.map +1 -0
  69. package/dist/storage/errors.js +27 -0
  70. package/dist/storage/errors.js.map +1 -0
  71. package/dist/storage/index.d.ts +7 -0
  72. package/dist/storage/index.d.ts.map +1 -0
  73. package/dist/storage/index.js +6 -0
  74. package/dist/storage/index.js.map +1 -0
  75. package/dist/storage/lifecycle.d.ts +5 -0
  76. package/dist/storage/lifecycle.d.ts.map +1 -0
  77. package/dist/storage/lifecycle.js +27 -0
  78. package/dist/storage/lifecycle.js.map +1 -0
  79. package/dist/storage/list.d.ts +8 -0
  80. package/dist/storage/list.d.ts.map +1 -0
  81. package/dist/storage/list.js +42 -0
  82. package/dist/storage/list.js.map +1 -0
  83. package/dist/storage/read.d.ts +9 -0
  84. package/dist/storage/read.d.ts.map +1 -0
  85. package/dist/storage/read.js +43 -0
  86. package/dist/storage/read.js.map +1 -0
  87. package/dist/storage/write.d.ts +8 -0
  88. package/dist/storage/write.d.ts.map +1 -0
  89. package/dist/storage/write.js +20 -0
  90. package/dist/storage/write.js.map +1 -0
  91. package/dist/workspace.d.ts +49 -0
  92. package/dist/workspace.d.ts.map +1 -0
  93. package/dist/workspace.js +280 -0
  94. package/dist/workspace.js.map +1 -0
  95. package/package.json +48 -0
  96. package/src/agent-docs/body.ts +562 -0
  97. package/src/agent-docs/content-hash.ts +25 -0
  98. package/src/agent-docs/index.ts +8 -0
  99. package/src/config.ts +69 -0
  100. package/src/forge-types.ts +167 -0
  101. package/src/index.ts +98 -0
  102. package/src/memory.ts +116 -0
  103. package/src/package.ts +120 -0
  104. package/src/paths.ts +30 -0
  105. package/src/records/any.ts +15 -0
  106. package/src/records/convention.ts +10 -0
  107. package/src/records/core.ts +55 -0
  108. package/src/records/decision.ts +10 -0
  109. package/src/records/failure.ts +11 -0
  110. package/src/records/glossary.ts +15 -0
  111. package/src/records/principle.ts +13 -0
  112. package/src/storage/errors.ts +23 -0
  113. package/src/storage/index.ts +10 -0
  114. package/src/storage/lifecycle.ts +38 -0
  115. package/src/storage/list.ts +53 -0
  116. package/src/storage/read.ts +54 -0
  117. package/src/storage/write.ts +32 -0
  118. package/src/workspace.ts +342 -0
@@ -0,0 +1,167 @@
1
+ // Meta Forge type + attribute name constants. Centralized for typo-safety
2
+ // per CLAUDE.md ("Named constants for metamodel strings — always") and so
3
+ // downstream readers (codegen prompts, MCP exposers) have one source of
4
+ // truth. See SP5 §4 for the design rationale.
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // New top-level types registered by registerForgeTypes()
8
+ // ---------------------------------------------------------------------------
9
+
10
+ export const FORGE_TYPE_DECISION = "decision";
11
+ export const FORGE_TYPE_PRINCIPLE = "principle";
12
+ export const FORGE_TYPE_CONVENTION = "convention";
13
+ export const FORGE_TYPE_GLOSSARY = "glossary";
14
+ export const FORGE_TYPE_FAILURE = "failure";
15
+
16
+ export const FORGE_TYPES = [
17
+ FORGE_TYPE_DECISION,
18
+ FORGE_TYPE_PRINCIPLE,
19
+ FORGE_TYPE_CONVENTION,
20
+ FORGE_TYPE_GLOSSARY,
21
+ FORGE_TYPE_FAILURE,
22
+ ] as const;
23
+ export type ForgeType = (typeof FORGE_TYPES)[number];
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Subtypes per type (open lists; "base" is always valid)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ export const FORGE_DECISION_SUBTYPES = ["base", "global", "scoped"] as const;
30
+ export const FORGE_PRINCIPLE_SUBTYPES = ["base", "advisory", "enforced"] as const;
31
+ export const FORGE_CONVENTION_SUBTYPES = ["base"] as const;
32
+ export const FORGE_GLOSSARY_SUBTYPES = ["base"] as const;
33
+ export const FORGE_FAILURE_SUBTYPES = ["base"] as const;
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // @forge* attribute names (camelCase, no separator)
37
+ // ---------------------------------------------------------------------------
38
+
39
+ // Universal (any forge-related node)
40
+ export const FORGE_ATTR_CONFIDENCE = "forgeConfidence";
41
+ export const FORGE_ATTR_SOURCE = "forgeSource";
42
+ export const FORGE_ATTR_CAPTURED_AT = "forgeCapturedAt";
43
+ export const FORGE_ATTR_LAST_VALIDATED_COMMIT = "forgeLastValidatedCommit";
44
+
45
+ // Object/entity
46
+ export const FORGE_ATTR_PRIMARY_LOCATION = "forgePrimaryLocation";
47
+ export const FORGE_ATTR_OCCURRENCES = "forgeOccurrences";
48
+
49
+ // Decision
50
+ export const FORGE_ATTR_RATIONALE = "forgeRationale";
51
+ export const FORGE_ATTR_ALTERNATIVES = "forgeAlternatives";
52
+ export const FORGE_ATTR_SCOPE = "forgeScope";
53
+
54
+ // Principle
55
+ export const FORGE_ATTR_STATEMENT = "forgeStatement";
56
+ export const FORGE_ATTR_ENFORCEMENT = "forgeEnforcement";
57
+
58
+ // Convention
59
+ export const FORGE_ATTR_PATTERN_DESCRIPTION = "forgePatternDescription";
60
+ export const FORGE_ATTR_EXAMPLES = "forgeExamples";
61
+ export const FORGE_ATTR_COUNTER_EXAMPLES = "forgeCounterExamples";
62
+ export const FORGE_ATTR_APPLIES_TO = "forgeAppliesTo";
63
+
64
+ // Glossary
65
+ export const FORGE_ATTR_TERM = "forgeTerm";
66
+ export const FORGE_ATTR_SYNONYMS = "forgeSynonyms";
67
+ export const FORGE_ATTR_DEFINITION = "forgeDefinition";
68
+ export const FORGE_ATTR_CODE_ANCHORS = "forgeCodeAnchors";
69
+ export const FORGE_ATTR_SEE_ALSO = "forgeSeeAlso";
70
+
71
+ // Failure
72
+ export const FORGE_ATTR_WHAT_WAS_TRIED = "forgeWhatWasTried";
73
+ export const FORGE_ATTR_WHY_IT_FAILED = "forgeWhyItFailed";
74
+
75
+ export const FORGE_ATTRS = [
76
+ FORGE_ATTR_CONFIDENCE,
77
+ FORGE_ATTR_SOURCE,
78
+ FORGE_ATTR_CAPTURED_AT,
79
+ FORGE_ATTR_LAST_VALIDATED_COMMIT,
80
+ FORGE_ATTR_PRIMARY_LOCATION,
81
+ FORGE_ATTR_OCCURRENCES,
82
+ FORGE_ATTR_RATIONALE,
83
+ FORGE_ATTR_ALTERNATIVES,
84
+ FORGE_ATTR_SCOPE,
85
+ FORGE_ATTR_STATEMENT,
86
+ FORGE_ATTR_ENFORCEMENT,
87
+ FORGE_ATTR_PATTERN_DESCRIPTION,
88
+ FORGE_ATTR_EXAMPLES,
89
+ FORGE_ATTR_COUNTER_EXAMPLES,
90
+ FORGE_ATTR_APPLIES_TO,
91
+ FORGE_ATTR_TERM,
92
+ FORGE_ATTR_SYNONYMS,
93
+ FORGE_ATTR_DEFINITION,
94
+ FORGE_ATTR_CODE_ANCHORS,
95
+ FORGE_ATTR_SEE_ALSO,
96
+ FORGE_ATTR_WHAT_WAS_TRIED,
97
+ FORGE_ATTR_WHY_IT_FAILED,
98
+ ] as const;
99
+ export type ForgeAttr = (typeof FORGE_ATTRS)[number];
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // registerForgeTypes
103
+ // ---------------------------------------------------------------------------
104
+
105
+ import {
106
+ type ChildRule,
107
+ type TypeDefinition,
108
+ TypeId,
109
+ TypeRegistry,
110
+ MetaData,
111
+ TYPE_ATTR,
112
+ CHILD_RULE_WILDCARD,
113
+ } from "@metaobjectsdev/metadata";
114
+
115
+ /** Minimal concrete MetaData subclass used for all forge descriptive nodes. */
116
+ class MetaForgeNode extends MetaData {}
117
+
118
+ function wildcardOf(childType: string): ChildRule {
119
+ return {
120
+ childType,
121
+ childSubType: CHILD_RULE_WILDCARD,
122
+ childName: CHILD_RULE_WILDCARD,
123
+ };
124
+ }
125
+
126
+ function def(
127
+ type: string,
128
+ subType: string,
129
+ description: string,
130
+ childRules: ChildRule[],
131
+ ): TypeDefinition {
132
+ return {
133
+ typeId: new TypeId(type, subType),
134
+ description,
135
+ factory: (typeId, name) => new MetaForgeNode(typeId, name),
136
+ childRules,
137
+ attributes: [],
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Register Meta Forge's five descriptive top-level types into the given
143
+ * registry. Must be called AFTER `registerCoreTypes()`.
144
+ *
145
+ * Each type accepts `attr` children for expanded-form @forge* attributes.
146
+ * Loader's default permissive mode tolerates these new types as children
147
+ * of the metadata wrapper (unknowns are warned, not errored).
148
+ */
149
+ export function registerForgeTypes(registry: TypeRegistry): void {
150
+ const forgeChildRules = [wildcardOf(TYPE_ATTR)];
151
+
152
+ for (const subType of FORGE_DECISION_SUBTYPES) {
153
+ registry.register(def(FORGE_TYPE_DECISION, subType, `Forge decision (${subType})`, forgeChildRules));
154
+ }
155
+ for (const subType of FORGE_PRINCIPLE_SUBTYPES) {
156
+ registry.register(def(FORGE_TYPE_PRINCIPLE, subType, `Forge principle (${subType})`, forgeChildRules));
157
+ }
158
+ for (const subType of FORGE_CONVENTION_SUBTYPES) {
159
+ registry.register(def(FORGE_TYPE_CONVENTION, subType, `Forge convention (${subType})`, forgeChildRules));
160
+ }
161
+ for (const subType of FORGE_GLOSSARY_SUBTYPES) {
162
+ registry.register(def(FORGE_TYPE_GLOSSARY, subType, `Forge glossary entry (${subType})`, forgeChildRules));
163
+ }
164
+ for (const subType of FORGE_FAILURE_SUBTYPES) {
165
+ registry.register(def(FORGE_TYPE_FAILURE, subType, `Forge failure record (${subType})`, forgeChildRules));
166
+ }
167
+ }
package/src/index.ts ADDED
@@ -0,0 +1,98 @@
1
+ // Records
2
+ export { RecordCore, RecordType, RecordSource } from "./records/core.js";
3
+ export { ConventionRecord } from "./records/convention.js";
4
+ export { DecisionRecord } from "./records/decision.js";
5
+ export { PrincipleRecord } from "./records/principle.js";
6
+ export { GlossaryRecord } from "./records/glossary.js";
7
+ export { FailureRecord } from "./records/failure.js";
8
+ export { AnyRecord } from "./records/any.js";
9
+
10
+ // Storage
11
+ export {
12
+ readRecord,
13
+ recordExists,
14
+ writeRecord,
15
+ removeRecord,
16
+ listRecords,
17
+ promoteRecord,
18
+ supersede,
19
+ MetaForgeRecordNotFoundError,
20
+ MetaForgeAlreadyPromotedError,
21
+ MetaForgeRecordParseError,
22
+ } from "./storage/index.js";
23
+ export type { ListOptions } from "./storage/index.js";
24
+
25
+ // Paths
26
+ export { recordPath, resolveMetaRoot } from "./paths.js";
27
+
28
+ // Config
29
+ export { ConfigSchema, DEFAULT_CONFIG, loadConfig, saveConfig } from "./config.js";
30
+ export type { Config } from "./config.js";
31
+
32
+ // Meta Forge metadata types + attribute name constants (registered into a
33
+ // TypeRegistry to let Loader parse decision/principle/etc. children + the
34
+ // @forge* attribute namespace).
35
+ export {
36
+ // Type names
37
+ FORGE_TYPE_DECISION,
38
+ FORGE_TYPE_PRINCIPLE,
39
+ FORGE_TYPE_CONVENTION,
40
+ FORGE_TYPE_GLOSSARY,
41
+ FORGE_TYPE_FAILURE,
42
+ FORGE_TYPES,
43
+ // Subtypes
44
+ FORGE_DECISION_SUBTYPES,
45
+ FORGE_PRINCIPLE_SUBTYPES,
46
+ FORGE_CONVENTION_SUBTYPES,
47
+ FORGE_GLOSSARY_SUBTYPES,
48
+ FORGE_FAILURE_SUBTYPES,
49
+ // Attribute names
50
+ FORGE_ATTR_CONFIDENCE,
51
+ FORGE_ATTR_SOURCE,
52
+ FORGE_ATTR_CAPTURED_AT,
53
+ FORGE_ATTR_LAST_VALIDATED_COMMIT,
54
+ FORGE_ATTR_PRIMARY_LOCATION,
55
+ FORGE_ATTR_OCCURRENCES,
56
+ FORGE_ATTR_RATIONALE,
57
+ FORGE_ATTR_ALTERNATIVES,
58
+ FORGE_ATTR_SCOPE,
59
+ FORGE_ATTR_STATEMENT,
60
+ FORGE_ATTR_ENFORCEMENT,
61
+ FORGE_ATTR_PATTERN_DESCRIPTION,
62
+ FORGE_ATTR_EXAMPLES,
63
+ FORGE_ATTR_COUNTER_EXAMPLES,
64
+ FORGE_ATTR_APPLIES_TO,
65
+ FORGE_ATTR_TERM,
66
+ FORGE_ATTR_SYNONYMS,
67
+ FORGE_ATTR_DEFINITION,
68
+ FORGE_ATTR_CODE_ANCHORS,
69
+ FORGE_ATTR_SEE_ALSO,
70
+ FORGE_ATTR_WHAT_WAS_TRIED,
71
+ FORGE_ATTR_WHY_IT_FAILED,
72
+ FORGE_ATTRS,
73
+ // Registration helper
74
+ registerForgeTypes,
75
+ } from "./forge-types.js";
76
+ export type { ForgeType, ForgeAttr } from "./forge-types.js";
77
+
78
+ // Memory loader — read metaobjects/ into a MetaData tree
79
+ // (workspace-aware: walks extends: deps via pnpm-workspace.yaml or
80
+ // package.json workspaces field if present)
81
+ export { loadMemory, DEFAULT_METADATA_DIR, DEFAULT_METAOBJECTS_DIR } from "./memory.js";
82
+
83
+ // Workspace discovery — finds peer metadata packages in a monorepo
84
+ export { discoverWorkspace, resolveExtendsOrder, packageLabel } from "./workspace.js";
85
+ export type { Workspace, WorkspacePackage } from "./workspace.js";
86
+
87
+ // Package manifest — the v0.3 package.meta.json model. Three-field manifest
88
+ // (name, version, extends) that defines a metadata package's identity and
89
+ // upstream dependencies. The package's metadata tree IS its public API;
90
+ // there is no exports field. See docs/strategy/2026-05-12-v0.3-ai-first-
91
+ // metadata-loading.md.
92
+ export {
93
+ PackageManifestSchema,
94
+ readPackageManifest,
95
+ resolveMetaobjectsPackage,
96
+ PACKAGE_MANIFEST_FILE,
97
+ } from "./package.js";
98
+ export type { PackageManifest } from "./package.js";
package/src/memory.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { join } from "node:path";
2
+ import { readdir, stat } from "node:fs/promises";
3
+ import { FileMetaDataLoader } from "@metaobjectsdev/metadata/core";
4
+ import {
5
+ TypeRegistry,
6
+ registerCoreTypes,
7
+ type MetaRoot,
8
+ } from "@metaobjectsdev/metadata";
9
+ import { registerForgeTypes } from "./forge-types.js";
10
+ import { discoverWorkspace, resolveExtendsOrder } from "./workspace.js";
11
+
12
+ /**
13
+ * Default directory name (relative to project root) where metadata JSON files
14
+ * are scanned. Scaffold via `meta init`; the directory is committed to git.
15
+ */
16
+ export const DEFAULT_METADATA_DIR = "metaobjects";
17
+
18
+ /**
19
+ * Default directory name (relative to project root) for MetaObjects' own
20
+ * runtime state: config.json, .gen-state/, package.meta.json, agent docs.
21
+ * Scaffold via `meta init`; most contents are committed to git.
22
+ */
23
+ export const DEFAULT_METAOBJECTS_DIR = ".metaobjects";
24
+
25
+ /**
26
+ * Load all metadata files from `<repoRoot>/metaobjects/` into a single
27
+ * MetaData. If `<repoRoot>/.meta/package.meta.json` declares `extends:` deps
28
+ * and a workspace can be discovered (pnpm-workspace.yaml or package.json
29
+ * workspaces), peer packages are loaded too in topological dep-first order.
30
+ *
31
+ * Excludes `_pending/`. Registers metaobjects core types plus Meta Forge's
32
+ * descriptive top-level types (decision, principle, etc.) so mixed content
33
+ * parses without warnings.
34
+ *
35
+ * Throws if `metaobjects/` doesn't exist (callers should run `meta init`).
36
+ *
37
+ * @param repoRoot The project's working-directory root (e.g. process.cwd()).
38
+ * `loadMemory` resolves `metaobjects/` and (if workspace-aware) the
39
+ * transitive `extends:` graph automatically.
40
+ */
41
+ export async function loadMemory(repoRoot: string): Promise<MetaRoot> {
42
+ const registry = new TypeRegistry();
43
+ registerCoreTypes(registry);
44
+ registerForgeTypes(registry);
45
+
46
+ // Collect all metadata file paths to load. Order matters for the parser's
47
+ // deferred-resolution pass (it parses in array order, then resolves supers
48
+ // against the merged tree afterwards) — dep packages first, current last.
49
+ const paths = await collectMetadataPaths(repoRoot);
50
+
51
+ const loader = new FileMetaDataLoader({ registry });
52
+ const result = await loader.loadFiles(paths);
53
+
54
+ if (result.errors.length > 0) {
55
+ const first = result.errors[0]!;
56
+ throw first;
57
+ }
58
+
59
+ return result.root;
60
+ }
61
+
62
+ // Dep packages' metaobjects/ files first (topological order), then current.
63
+ async function collectMetadataPaths(repoRoot: string): Promise<string[]> {
64
+ const currentMetaDir = join(repoRoot, ".meta");
65
+ const ws = await discoverWorkspace(repoRoot);
66
+
67
+ // Workspace path: walk extends, load dep metaobjects/ dirs first
68
+ if (ws !== undefined) {
69
+ const currentPkg = ws.packages.find((p) => p.metaDir === currentMetaDir);
70
+ if (currentPkg !== undefined && currentPkg.manifest.extends.length > 0) {
71
+ const ordered = resolveExtendsOrder(ws, currentMetaDir);
72
+ const paths: string[] = [];
73
+ for (const pkg of ordered) {
74
+ // Each workspace package's metadata lives alongside its .meta/ dir
75
+ const pkgRoot = join(pkg.metaDir, "..");
76
+ paths.push(...(await listJsonFiles(join(pkgRoot, DEFAULT_METADATA_DIR))));
77
+ }
78
+ return paths;
79
+ }
80
+ }
81
+
82
+ // Single-package path: scan metaobjects/ at the project root
83
+ return listJsonFiles(join(repoRoot, DEFAULT_METADATA_DIR));
84
+ }
85
+
86
+ /**
87
+ * Recursively list *.json files under a directory, excluding _pending/ at
88
+ * any level. Subdirectories (e.g. projections/) are walked depth-first.
89
+ * Files within a directory are sorted alphabetically for deterministic load
90
+ * order; subdirectories are visited after files at the same level.
91
+ */
92
+ async function listJsonFiles(dir: string): Promise<string[]> {
93
+ let entries: string[];
94
+ try {
95
+ entries = await readdir(dir);
96
+ } catch (err) {
97
+ throw new Error(`loadMemory: cannot read ${dir}: ${(err as Error).message}`);
98
+ }
99
+ const paths: string[] = [];
100
+ const subdirs: string[] = [];
101
+ for (const entry of entries) {
102
+ if (entry === "_pending") continue;
103
+ const full = join(dir, entry);
104
+ const s = await stat(full);
105
+ if (s.isDirectory()) {
106
+ subdirs.push(full);
107
+ } else if (s.isFile() && entry.endsWith(".json")) {
108
+ paths.push(full);
109
+ }
110
+ }
111
+ // Recurse into subdirectories after collecting files at this level
112
+ for (const sub of subdirs.sort()) {
113
+ paths.push(...(await listJsonFiles(sub)));
114
+ }
115
+ return paths;
116
+ }
package/src/package.ts ADDED
@@ -0,0 +1,120 @@
1
+ import { z } from "zod";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { join } from "node:path";
4
+
5
+ // Per the v0.3 AI-first metadata loading strategy: a package.meta.json is the
6
+ // boundary between metadata bundles. Three fields total — name, version,
7
+ // extends. No exports list; no @private. The package's metadata tree IS its
8
+ // public API. See docs/strategy/2026-05-12-v0.3-ai-first-metadata-loading.md
9
+ // §4.3.
10
+
11
+ /**
12
+ * Validates a package version string. npm-compatible: semver basics
13
+ * (major.minor.patch with optional prerelease/build). Not a full semver
14
+ * parser — strict-enough for now.
15
+ */
16
+ const VersionStringSchema = z.string().regex(
17
+ /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/,
18
+ "package version must follow semver (e.g. 1.2.0 or 1.2.0-alpha.1)",
19
+ );
20
+
21
+ /**
22
+ * Validates a package extends entry. Either a bare package name or
23
+ * a name with a semver range — npm-style. Range parsing is shallow
24
+ * for v0.3 v0.1; full range resolution lands when multi-package
25
+ * loading actually ships.
26
+ */
27
+ const ExtendsEntrySchema = z.string().min(1);
28
+
29
+ /**
30
+ * Validates a canonical metaobjects package ref: snake_case segments
31
+ * separated by `::`. Examples: `trainer_website`, `acme::common::user_mgmt`.
32
+ * Used in metadata file refs (extends:, super:) cross-language.
33
+ */
34
+ const MetaobjectsPackageSchema = z.string().regex(
35
+ /^[a-z][a-z0-9_]*(::[a-z][a-z0-9_]*)*$/,
36
+ "metaobjectsPackage must be snake_case segments separated by :: (e.g. acme::common::user_mgmt)",
37
+ );
38
+
39
+ /**
40
+ * The package.meta.json schema. Authoring this is the one-time cost of
41
+ * defining a metadata package; subsequent edits should be rare.
42
+ */
43
+ export const PackageManifestSchema = z.object({
44
+ /**
45
+ * Canonical name of the package in the language's native package manager.
46
+ * Conventionally `@scope/name` (npm-style) for TS, but bare names also
47
+ * work. Each language's package config (pom.xml, .csproj, pyproject.toml)
48
+ * uses its own native field for this — only TS uses `name` here.
49
+ */
50
+ name: z.string().min(1),
51
+
52
+ /** Semver version of this package. */
53
+ version: VersionStringSchema,
54
+
55
+ /**
56
+ * Optional cross-language canonical ref for this package, used inside
57
+ * metadata files (extends:, super:) and for cross-language consumption.
58
+ * If omitted, derived from `name` via a simple rule: `@scope/name` →
59
+ * `scope::name` (strip @, replace `/` with `::`). Hyphens stay as-is in
60
+ * the auto-derived form — for hierarchical naming users declare this
61
+ * field explicitly.
62
+ *
63
+ * Per v0.3 strategy doc §8.2.
64
+ */
65
+ metaobjectsPackage: MetaobjectsPackageSchema.optional(),
66
+
67
+ /**
68
+ * Other packages whose metadata trees this package extends. Each entry is
69
+ * a package name; the loader resolves it to the matching peer package.
70
+ * Empty/omitted means this package has no upstream dependencies.
71
+ */
72
+ extends: z.array(ExtendsEntrySchema).default([]),
73
+ });
74
+
75
+ export type PackageManifest = z.infer<typeof PackageManifestSchema>;
76
+
77
+ export const PACKAGE_MANIFEST_FILE = "package.meta.json";
78
+
79
+ /**
80
+ * Read a package.meta.json from a given .meta/ root, if present.
81
+ * Returns undefined when the file isn't there — packages are optional in
82
+ * v0.3 v0.1 (loadMemory still works for single-package usage without one,
83
+ * and forge init scaffolds one but doesn't enforce its presence).
84
+ */
85
+ export async function readPackageManifest(metaDir: string): Promise<PackageManifest | undefined> {
86
+ const path = join(metaDir, PACKAGE_MANIFEST_FILE);
87
+ try {
88
+ const s = await stat(path);
89
+ if (!s.isFile()) return undefined;
90
+ } catch {
91
+ return undefined;
92
+ }
93
+ const raw = await readFile(path, "utf8");
94
+ return PackageManifestSchema.parse(JSON.parse(raw));
95
+ }
96
+
97
+ /**
98
+ * Return the canonical metaobjects package ref for a manifest. Uses the
99
+ * explicit `metaobjectsPackage` field if set; otherwise derives from `name`
100
+ * via the simple rule: `@scope/foo-bar` → `scope::foo-bar` (strip @,
101
+ * replace `/` with `::`, leave hyphens). For hierarchical or non-trivial
102
+ * naming users declare `metaobjectsPackage` explicitly.
103
+ *
104
+ * Returns undefined if `name` itself isn't a valid bare name (e.g. has
105
+ * uppercase chars) and no explicit ref is set — the caller should treat
106
+ * this as an authoring error.
107
+ */
108
+ export function resolveMetaobjectsPackage(manifest: PackageManifest): string | undefined {
109
+ if (manifest.metaobjectsPackage !== undefined) return manifest.metaobjectsPackage;
110
+ // Auto-derive from name: strip leading @, replace / with ::, lowercase
111
+ // remains untouched — npm names are already lowercase per registry rules.
112
+ const stripped = manifest.name.replace(/^@/, "");
113
+ const derived = stripped.replace(/\//g, "::");
114
+ // Validate that the derived form is a legal canonical ref. If not,
115
+ // return undefined so the caller can surface a clear error.
116
+ if (!/^[a-z][a-z0-9_-]*(::[a-z][a-z0-9_-]*)*$/.test(derived)) {
117
+ return undefined;
118
+ }
119
+ return derived;
120
+ }
package/src/paths.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { join, dirname, resolve, parse } from "node:path";
3
+
4
+ export function recordPath(
5
+ metaRoot: string,
6
+ type: string,
7
+ id: string,
8
+ opts: { pending?: boolean } = {},
9
+ ): string {
10
+ const segments = opts.pending
11
+ ? ["memory", "_pending", type, `${id}.json`]
12
+ : ["memory", type, `${id}.json`];
13
+ return join(metaRoot, ...segments);
14
+ }
15
+
16
+ export async function resolveMetaRoot(startDir: string): Promise<string> {
17
+ let current = resolve(startDir);
18
+ const root = parse(current).root;
19
+ while (current !== root) {
20
+ const candidate = join(current, ".meta");
21
+ try {
22
+ const s = await stat(candidate);
23
+ if (s.isDirectory()) return candidate;
24
+ } catch {
25
+ // not present at this level
26
+ }
27
+ current = dirname(current);
28
+ }
29
+ throw new Error(`no .meta directory found walking up from ${startDir}`);
30
+ }
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ import { ConventionRecord } from "./convention.js";
3
+ import { DecisionRecord } from "./decision.js";
4
+ import { PrincipleRecord } from "./principle.js";
5
+ import { GlossaryRecord } from "./glossary.js";
6
+ import { FailureRecord } from "./failure.js";
7
+
8
+ export const AnyRecord = z.discriminatedUnion("type", [
9
+ ConventionRecord,
10
+ DecisionRecord,
11
+ PrincipleRecord,
12
+ GlossaryRecord,
13
+ FailureRecord,
14
+ ]);
15
+ export type AnyRecord = z.infer<typeof AnyRecord>;
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ import { RecordCore } from "./core.js";
3
+
4
+ export const ConventionRecord = RecordCore.extend({
5
+ type: z.literal("convention"),
6
+ pattern_description: z.string(),
7
+ examples: z.array(z.string()),
8
+ applies_to: z.array(z.string()),
9
+ });
10
+ export type ConventionRecord = z.infer<typeof ConventionRecord>;
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+
3
+ export const RecordType = z.enum([
4
+ "convention",
5
+ "decision",
6
+ "principle",
7
+ "glossary",
8
+ "failure",
9
+ ]);
10
+ export type RecordType = z.infer<typeof RecordType>;
11
+
12
+ export const RecordSource = z.enum([
13
+ "ts-ast",
14
+ "drizzle",
15
+ "prisma",
16
+ "openapi",
17
+ "human",
18
+ "claude",
19
+ "llm-from-commits",
20
+ "llm-from-prs",
21
+ "ingest:ts-ast",
22
+ "ingest:drizzle",
23
+ "ingest:zod",
24
+ ]);
25
+ export type RecordSource = z.infer<typeof RecordSource>;
26
+
27
+ export const RecordCore = z.object({
28
+ schema_version: z.literal(1),
29
+ type: RecordType,
30
+ id: z.string().regex(/^[a-z0-9][a-z0-9-]*$/),
31
+ title: z.string(),
32
+ confidence: z.number().min(0).max(1),
33
+ source: RecordSource,
34
+ captured_at: z.string().datetime(),
35
+ last_validated_against_commit: z.string(),
36
+ superseded_by: z.string().optional(),
37
+ evidence: z
38
+ .object({
39
+ commits: z.array(z.string()).optional(),
40
+ prs: z.array(z.string()).optional(),
41
+ conversation_id: z.string().optional(),
42
+ })
43
+ .optional(),
44
+ deviations: z
45
+ .array(
46
+ z.object({
47
+ when: z.string().datetime(),
48
+ why: z.string(),
49
+ conversation_id: z.string().optional(),
50
+ }),
51
+ )
52
+ .default([]),
53
+ });
54
+
55
+ export type RecordCore = z.infer<typeof RecordCore>;
@@ -0,0 +1,10 @@
1
+ import { z } from "zod";
2
+ import { RecordCore } from "./core.js";
3
+
4
+ export const DecisionRecord = RecordCore.extend({
5
+ type: z.literal("decision"),
6
+ rationale: z.string(),
7
+ alternatives_considered: z.array(z.string()),
8
+ scope: z.union([z.literal("global"), z.array(z.string())]),
9
+ });
10
+ export type DecisionRecord = z.infer<typeof DecisionRecord>;
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ import { RecordCore } from "./core.js";
3
+
4
+ export const FailureRecord = RecordCore.extend({
5
+ type: z.literal("failure"),
6
+ what_was_tried: z.string(),
7
+ why_it_failed: z.string(),
8
+ do_not_repeat_in: z.array(z.string()).optional(),
9
+ related_decisions: z.array(z.string()).optional(),
10
+ });
11
+ export type FailureRecord = z.infer<typeof FailureRecord>;
@@ -0,0 +1,15 @@
1
+ import { z } from "zod";
2
+ import { RecordCore } from "./core.js";
3
+
4
+ export const GlossaryRecord = RecordCore.extend({
5
+ type: z.literal("glossary"),
6
+ term: z.string(),
7
+ synonyms: z.array(z.string()),
8
+ definition: z.string(),
9
+ code_anchors: z.object({
10
+ entity: z.string().optional(),
11
+ files: z.array(z.string()).optional(),
12
+ }),
13
+ see_also: z.array(z.string()),
14
+ });
15
+ export type GlossaryRecord = z.infer<typeof GlossaryRecord>;
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ import { RecordCore } from "./core.js";
3
+
4
+ export const PrincipleRecord = RecordCore.extend({
5
+ type: z.literal("principle"),
6
+ statement: z.string(),
7
+ rationale: z.string(),
8
+ scope: z.array(z.string()),
9
+ examples: z.array(z.string()),
10
+ counter_examples: z.array(z.string()),
11
+ enforcement: z.enum(["advisory", "block"]).default("advisory"),
12
+ });
13
+ export type PrincipleRecord = z.infer<typeof PrincipleRecord>;