@ultimat3/manifest 21.0.0 → 22.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -150,3 +150,15 @@ never writes, and it never synthesises a sentence.
150
150
  | `X_MANIFEST_BREAKING` | contract broke with no major bump | bump the version, or restore |
151
151
  | `X_AGENTS_MD_MISSING` | no `AGENTS.md` | write one by hand |
152
152
  | `X_AGENTS_MD_TOO_LARGE` | over the byte budget | move facts to `x.manifest.json` |
153
+
154
+ ### Error classes
155
+
156
+ Every error class `src/index.ts` exports, for `instanceof` inside one process. Across a wire or
157
+ a job boundary the class is gone and the `code` is what survives — match on that.
158
+
159
+ | Class | Code | Declared in |
160
+ |---|---|---|
161
+ | `AgentsMdMissingError` | `X_AGENTS_MD_MISSING` | `src/errors.ts` |
162
+ | `AgentsMdTooLargeError` | `X_AGENTS_MD_TOO_LARGE` | `src/errors.ts` |
163
+ | `ManifestBreakingError` | `X_MANIFEST_BREAKING` | `src/errors.ts` |
164
+ | `ManifestDriftError` | `X_MANIFEST_DRIFT` | `src/errors.ts` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/manifest",
3
- "version": "21.0.0",
3
+ "version": "22.1.0",
4
4
  "description": "x.manifest.json: deterministic generated facts, contract diff, AGENTS.md budget",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,11 +31,11 @@
31
31
  "test": "bun test"
32
32
  },
33
33
  "dependencies": {
34
- "@ultimat3/action": "21.0.0",
35
- "@ultimat3/core": "21.0.0",
36
- "@ultimat3/entity": "21.0.0",
37
- "@ultimat3/jobs": "21.0.0",
38
- "@ultimat3/query": "21.0.0",
39
- "@ultimat3/realtime": "21.0.0"
34
+ "@ultimat3/action": "22.1.0",
35
+ "@ultimat3/core": "22.1.0",
36
+ "@ultimat3/entity": "22.1.0",
37
+ "@ultimat3/jobs": "22.1.0",
38
+ "@ultimat3/query": "22.1.0",
39
+ "@ultimat3/realtime": "22.1.0"
40
40
  }
41
41
  }
package/src/agents-md.ts CHANGED
@@ -81,13 +81,31 @@ export async function assertAgentsMd(input: CheckAgentsMdInput = {}): Promise<Ag
81
81
  return check;
82
82
  }
83
83
 
84
+ /** A header naming a column of generated facts. A conventions table (`| Rule | Detail |`) names none. */
85
+ const FACT_COLUMN = /\b(column|entity|entities|route|routes|endpoint|endpoints|table|type)\b/i;
86
+
87
+ /** A table separator row: `| --- | :-: |`, pipes, dashes, colons and spaces only. */
88
+ const SEPARATOR = /^\s*\|[\s|:-]+\|\s*$/;
89
+
90
+ /**
91
+ * The HEADER row of every markdown table — the line directly above a separator. The columns are
92
+ * what a table tabulates; a word in a cell is prose about a convention, which is what the file is
93
+ * for, and warning on it flagged the scaffold's own conventions table on every app (slice 11 i).
94
+ */
95
+ const tableHeaders = (text: string): readonly string[] => {
96
+ const lines = text.split('\n');
97
+ return lines.filter(
98
+ (line, index) => line.trim().startsWith('|') && SEPARATOR.test(lines[index + 1] ?? ''),
99
+ );
100
+ };
101
+
84
102
  /**
85
103
  * Warn when the file has started duplicating generated facts. Duplication is how a context
86
104
  * file goes stale: the copy and the source drift, and the agent believes the copy.
87
105
  */
88
106
  function warningsFor(text: string): readonly string[] {
89
107
  const warnings: string[] = [];
90
- if (/^\s*\|.*\|\s*$/m.test(text) && /\b(column|entity|route|endpoint)\b/i.test(text)) {
108
+ if (tableHeaders(text).some((header) => FACT_COLUMN.test(header))) {
91
109
  warnings.push('looks like it tabulates schema or route facts; those live in x.manifest.json');
92
110
  }
93
111
  if (text.split('\n').length > 200) {
@@ -30,7 +30,7 @@ export function diffChannels(
30
30
  changes.push({
31
31
  kind: 'breaking',
32
32
  path: `${path}.policy`,
33
- detail: `policy ${channel.policy ?? 'none'} -> ${next.policy ?? 'none'}`,
33
+ detail: `policy ${channel.policy} -> ${next.policy}`,
34
34
  });
35
35
  }
36
36
  changes.push(...diffPermissions(path, channel, next));
@@ -115,10 +115,17 @@ function diffColumns(
115
115
  const beforeColumns = index(before.columns, (c) => c.name);
116
116
  for (const column of after.columns) {
117
117
  if (!beforeColumns.has(column.name)) {
118
+ // A default is what makes a NOT NULL column safe to add: every existing row takes it and no
119
+ // writer that omits the column is refused.
120
+ const additive = column.nullable || column.hasDefault === true;
118
121
  changes.push({
119
- kind: column.nullable ? 'additive' : 'breaking',
122
+ kind: additive ? 'additive' : 'breaking',
120
123
  path: `${path}.columns.${column.name}`,
121
- detail: column.nullable ? 'column added' : 'NOT NULL column added with no default',
124
+ detail: column.nullable
125
+ ? 'column added'
126
+ : additive
127
+ ? 'column added with a default'
128
+ : 'NOT NULL column added with no default',
122
129
  });
123
130
  }
124
131
  }
package/src/index.ts CHANGED
@@ -16,8 +16,6 @@ export { diffManifest, formatDiff } from './diff';
16
16
  export type { DocEntry, DocEntryKind } from './docs-scan';
17
17
  export {
18
18
  headerComment,
19
- parseGuideSections,
20
- parseReExports,
21
19
  scanInstalledDocs,
22
20
  scanPackageDocs,
23
21
  shortName,
package/src/schema.ts CHANGED
@@ -47,6 +47,12 @@ export interface ColumnFact {
47
47
  readonly nullable: boolean;
48
48
  readonly primaryKey?: boolean;
49
49
  readonly references?: string;
50
+ /**
51
+ * The column has a declared default (a value, `defaultNow()`, a generated key). Written only when
52
+ * true, so a column without one reads as it always did. Adding a NOT NULL column WITH a default
53
+ * is additive — every existing row takes the default — and was classed breaking without this.
54
+ */
55
+ readonly hasDefault?: boolean;
50
56
  }
51
57
 
52
58
  export interface EntityFact {
@@ -123,7 +129,8 @@ export interface ChannelFact {
123
129
  /** Record types (entity names) the channel carries. */
124
130
  readonly records: readonly string[];
125
131
  readonly events: boolean;
126
- readonly policy: string | null;
132
+ /** The policy's label. Never null since 22.0.0 — `channel()` requires a policy. */
133
+ readonly policy: string;
127
134
  readonly permissions: readonly string[];
128
135
  }
129
136
 
package/src/sources.ts CHANGED
@@ -54,6 +54,7 @@ export function frameworkSources(input: FrameworkSourcesInput): ManifestSources
54
54
  type: column.kind,
55
55
  nullable: !column.notNull,
56
56
  primaryKey: column.primaryKey,
57
+ ...(column.hasDefault ? { hasDefault: true } : {}),
57
58
  ...(column.references === null ? {} : { references: column.references }),
58
59
  })),
59
60
  invariants: entity.invariants.map((invariant) => invariant.name),