@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 +12 -0
- package/package.json +7 -7
- package/src/agents-md.ts +19 -1
- package/src/diff-channels.ts +1 -1
- package/src/diff-entities.ts +9 -2
- package/src/index.ts +0 -2
- package/src/schema.ts +8 -1
- package/src/sources.ts +1 -0
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": "
|
|
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": "
|
|
35
|
-
"@ultimat3/core": "
|
|
36
|
-
"@ultimat3/entity": "
|
|
37
|
-
"@ultimat3/jobs": "
|
|
38
|
-
"@ultimat3/query": "
|
|
39
|
-
"@ultimat3/realtime": "
|
|
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 (
|
|
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) {
|
package/src/diff-channels.ts
CHANGED
|
@@ -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
|
|
33
|
+
detail: `policy ${channel.policy} -> ${next.policy}`,
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
changes.push(...diffPermissions(path, channel, next));
|
package/src/diff-entities.ts
CHANGED
|
@@ -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:
|
|
122
|
+
kind: additive ? 'additive' : 'breaking',
|
|
120
123
|
path: `${path}.columns.${column.name}`,
|
|
121
|
-
detail: column.nullable
|
|
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
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
|
-
|
|
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),
|