@saasontools/strauss-kb 0.1.3 → 0.1.4
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 +25 -1
- package/dist/{chunk-HYNAEAPM.js → chunk-EDH43Z7J.js} +67 -13
- package/dist/chunk-EDH43Z7J.js.map +1 -0
- package/dist/{chunk-FSI4Q2FD.js → chunk-TS26G7TL.js} +2 -2
- package/dist/{chunk-QLTB77W4.js → chunk-Y5C7Z2HG.js} +2 -2
- package/dist/cli-main.cjs +66 -12
- package/dist/cli-main.cjs.map +1 -1
- package/dist/cli-main.js +2 -2
- package/dist/index.cjs +66 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +3 -3
- package/dist/mcp-main.cjs +66 -12
- package/dist/mcp-main.cjs.map +1 -1
- package/dist/mcp-main.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-HYNAEAPM.js.map +0 -1
- /package/dist/{chunk-FSI4Q2FD.js.map → chunk-TS26G7TL.js.map} +0 -0
- /package/dist/{chunk-QLTB77W4.js.map → chunk-Y5C7Z2HG.js.map} +0 -0
package/dist/index.d.cts
CHANGED
|
@@ -334,6 +334,12 @@ type KbWriteInput = {
|
|
|
334
334
|
/** Replace an existing record rather than failing on the collision. */
|
|
335
335
|
overwrite?: boolean;
|
|
336
336
|
};
|
|
337
|
+
type KbWriteResult = KbRecord & {
|
|
338
|
+
/** Whether this write also marked prior records superseded. */
|
|
339
|
+
action: "created" | "superseded-prior";
|
|
340
|
+
/** `frontmatter.strauss_supersedes` ids that were actually marked. */
|
|
341
|
+
supersededIds: string[];
|
|
342
|
+
};
|
|
337
343
|
/**
|
|
338
344
|
* Reads and writes a knowledge bundle.
|
|
339
345
|
*
|
|
@@ -357,7 +363,7 @@ declare class KbStore {
|
|
|
357
363
|
* concept id, so a caller cannot produce a file whose identity disagrees with
|
|
358
364
|
* its contents.
|
|
359
365
|
*/
|
|
360
|
-
write(bundlePath: string, input: KbWriteInput, actor?: string): Promise<
|
|
366
|
+
write(bundlePath: string, input: KbWriteInput, actor?: string): Promise<KbWriteResult>;
|
|
361
367
|
/** One record by concept id, or null when it does not exist. */
|
|
362
368
|
read(bundlePath: string, conceptId: string): Promise<KbRecord | null>;
|
|
363
369
|
/**
|
|
@@ -447,6 +453,19 @@ declare class KbStore {
|
|
|
447
453
|
* knows which agent touched what. So a bad line is surfaced and left alone.
|
|
448
454
|
*/
|
|
449
455
|
readLog(bundlePath: string): Promise<ReturnType<typeof parseLog>>;
|
|
456
|
+
/**
|
|
457
|
+
* `markSuperseded`, tolerant of the two ways it legitimately doesn't land:
|
|
458
|
+
* a missing target (a broken link, legal per compose.ts) or a CAS conflict
|
|
459
|
+
* from a concurrent writer touching the same target. A conflict is retried
|
|
460
|
+
* a bounded number of times — each attempt re-reads the target fresh — and
|
|
461
|
+
* on the last, `false` reports "not marked" rather than throwing: the
|
|
462
|
+
* caller's own record is already published, so failing here would leave
|
|
463
|
+
* that publish unreported instead of undone. kb_validate's existing
|
|
464
|
+
* "not marked superseded" check is what surfaces the residue.
|
|
465
|
+
*/
|
|
466
|
+
private markSupersededRetrying;
|
|
467
|
+
/** The one-directional half of `supersede`: marks `conceptId` superseded. */
|
|
468
|
+
private markSuperseded;
|
|
450
469
|
private mutate;
|
|
451
470
|
/**
|
|
452
471
|
* Two guarantees, both about writers running in parallel.
|
package/dist/index.d.ts
CHANGED
|
@@ -334,6 +334,12 @@ type KbWriteInput = {
|
|
|
334
334
|
/** Replace an existing record rather than failing on the collision. */
|
|
335
335
|
overwrite?: boolean;
|
|
336
336
|
};
|
|
337
|
+
type KbWriteResult = KbRecord & {
|
|
338
|
+
/** Whether this write also marked prior records superseded. */
|
|
339
|
+
action: "created" | "superseded-prior";
|
|
340
|
+
/** `frontmatter.strauss_supersedes` ids that were actually marked. */
|
|
341
|
+
supersededIds: string[];
|
|
342
|
+
};
|
|
337
343
|
/**
|
|
338
344
|
* Reads and writes a knowledge bundle.
|
|
339
345
|
*
|
|
@@ -357,7 +363,7 @@ declare class KbStore {
|
|
|
357
363
|
* concept id, so a caller cannot produce a file whose identity disagrees with
|
|
358
364
|
* its contents.
|
|
359
365
|
*/
|
|
360
|
-
write(bundlePath: string, input: KbWriteInput, actor?: string): Promise<
|
|
366
|
+
write(bundlePath: string, input: KbWriteInput, actor?: string): Promise<KbWriteResult>;
|
|
361
367
|
/** One record by concept id, or null when it does not exist. */
|
|
362
368
|
read(bundlePath: string, conceptId: string): Promise<KbRecord | null>;
|
|
363
369
|
/**
|
|
@@ -447,6 +453,19 @@ declare class KbStore {
|
|
|
447
453
|
* knows which agent touched what. So a bad line is surfaced and left alone.
|
|
448
454
|
*/
|
|
449
455
|
readLog(bundlePath: string): Promise<ReturnType<typeof parseLog>>;
|
|
456
|
+
/**
|
|
457
|
+
* `markSuperseded`, tolerant of the two ways it legitimately doesn't land:
|
|
458
|
+
* a missing target (a broken link, legal per compose.ts) or a CAS conflict
|
|
459
|
+
* from a concurrent writer touching the same target. A conflict is retried
|
|
460
|
+
* a bounded number of times — each attempt re-reads the target fresh — and
|
|
461
|
+
* on the last, `false` reports "not marked" rather than throwing: the
|
|
462
|
+
* caller's own record is already published, so failing here would leave
|
|
463
|
+
* that publish unreported instead of undone. kb_validate's existing
|
|
464
|
+
* "not marked superseded" check is what surfaces the residue.
|
|
465
|
+
*/
|
|
466
|
+
private markSupersededRetrying;
|
|
467
|
+
/** The one-directional half of `supersede`: marks `conceptId` superseded. */
|
|
468
|
+
private markSuperseded;
|
|
450
469
|
private mutate;
|
|
451
470
|
/**
|
|
452
471
|
* Two guarantees, both about writers running in parallel.
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runKbCli
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-Y5C7Z2HG.js";
|
|
4
4
|
import {
|
|
5
5
|
createKbMcpServer,
|
|
6
6
|
runKbMcpServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-TS26G7TL.js";
|
|
8
8
|
import {
|
|
9
9
|
BaseError,
|
|
10
10
|
CONTEXT_BEGIN,
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
trace,
|
|
81
81
|
unpinBase,
|
|
82
82
|
validateBundle
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-EDH43Z7J.js";
|
|
84
84
|
|
|
85
85
|
// src/match-diff.ts
|
|
86
86
|
function matchToDiff(files, records, options = {}) {
|
package/dist/mcp-main.cjs
CHANGED
|
@@ -214,7 +214,7 @@ var composeInputSchema = import_zod2.z.object({
|
|
|
214
214
|
/** Concept ids this record relates to; rendered as body links. */
|
|
215
215
|
relatedConceptIds: import_zod2.z.array(kbConceptIdSchema).optional(),
|
|
216
216
|
/** Concept ids this record replaces. The store settles the backlinks. */
|
|
217
|
-
supersedes: import_zod2.z.array(kbConceptIdSchema).optional(),
|
|
217
|
+
supersedes: import_zod2.z.array(kbConceptIdSchema).max(32).optional(),
|
|
218
218
|
materiality: import_zod2.z.enum(KB_MATERIALITIES).optional(),
|
|
219
219
|
confidence: import_zod2.z.enum(KB_CONFIDENCES).optional(),
|
|
220
220
|
owner: import_zod2.z.string().min(1).optional()
|
|
@@ -1602,7 +1602,11 @@ var writeCommand = define({
|
|
|
1602
1602
|
composeRecord(type, input, actor, now()),
|
|
1603
1603
|
actor
|
|
1604
1604
|
);
|
|
1605
|
-
return {
|
|
1605
|
+
return {
|
|
1606
|
+
conceptId: record.conceptId,
|
|
1607
|
+
action: record.action,
|
|
1608
|
+
supersededIds: record.supersededIds
|
|
1609
|
+
};
|
|
1606
1610
|
}
|
|
1607
1611
|
});
|
|
1608
1612
|
|
|
@@ -1632,7 +1636,11 @@ var writeDecisionCommand = define({
|
|
|
1632
1636
|
composeDecisionRecord(input, actor, now()),
|
|
1633
1637
|
actor
|
|
1634
1638
|
);
|
|
1635
|
-
return {
|
|
1639
|
+
return {
|
|
1640
|
+
conceptId: record.conceptId,
|
|
1641
|
+
action: record.action,
|
|
1642
|
+
supersededIds: record.supersededIds
|
|
1643
|
+
};
|
|
1636
1644
|
}
|
|
1637
1645
|
});
|
|
1638
1646
|
|
|
@@ -1723,7 +1731,7 @@ var KbRecordAlreadyExistsError = class extends BaseError {
|
|
|
1723
1731
|
fault: "User" /* User */,
|
|
1724
1732
|
retriable: false,
|
|
1725
1733
|
reportToUser: true,
|
|
1726
|
-
details: { conceptId: conceptId2 }
|
|
1734
|
+
details: { conceptId: conceptId2, action: "refused" }
|
|
1727
1735
|
});
|
|
1728
1736
|
this.conceptId = conceptId2;
|
|
1729
1737
|
}
|
|
@@ -1903,13 +1911,27 @@ var KbStore = class {
|
|
|
1903
1911
|
conceptId: conceptId2,
|
|
1904
1912
|
by: actor
|
|
1905
1913
|
});
|
|
1914
|
+
const targets = new Set(frontmatter.strauss_supersedes ?? []);
|
|
1915
|
+
targets.delete(conceptId2);
|
|
1916
|
+
const supersededIds = [];
|
|
1917
|
+
for (const old of targets) {
|
|
1918
|
+
if (await this.markSupersededRetrying(bundlePath2, old, conceptId2, actor)) {
|
|
1919
|
+
supersededIds.push(old);
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1906
1922
|
this.logger.info?.({
|
|
1907
1923
|
operation: "kb.write",
|
|
1908
1924
|
bundlePath: root,
|
|
1909
1925
|
conceptId: conceptId2,
|
|
1910
1926
|
anchors: frontmatter.strauss_anchors?.length ?? 0
|
|
1911
1927
|
});
|
|
1912
|
-
return {
|
|
1928
|
+
return {
|
|
1929
|
+
conceptId: conceptId2,
|
|
1930
|
+
frontmatter,
|
|
1931
|
+
body: input.body,
|
|
1932
|
+
action: supersededIds.length ? "superseded-prior" : "created",
|
|
1933
|
+
supersededIds
|
|
1934
|
+
};
|
|
1913
1935
|
}
|
|
1914
1936
|
/** One record by concept id, or null when it does not exist. */
|
|
1915
1937
|
async read(bundlePath2, conceptId2) {
|
|
@@ -1973,15 +1995,11 @@ var KbStore = class {
|
|
|
1973
1995
|
async supersede(bundlePath2, conceptId2, replacementId, actor = "unknown") {
|
|
1974
1996
|
const replacement = await this.read(bundlePath2, replacementId);
|
|
1975
1997
|
if (!replacement) throw new KbRecordNotFoundError(replacementId);
|
|
1976
|
-
const superseded = await this.
|
|
1998
|
+
const superseded = await this.markSuperseded(
|
|
1977
1999
|
bundlePath2,
|
|
1978
2000
|
conceptId2,
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
strauss_status: "superseded",
|
|
1982
|
-
strauss_superseded_by: replacementId
|
|
1983
|
-
}),
|
|
1984
|
-
{ operation: "supersede", by: actor, target: replacementId }
|
|
2001
|
+
replacementId,
|
|
2002
|
+
actor
|
|
1985
2003
|
);
|
|
1986
2004
|
await this.mutate(
|
|
1987
2005
|
bundlePath2,
|
|
@@ -2145,6 +2163,42 @@ ${answer}
|
|
|
2145
2163
|
}
|
|
2146
2164
|
return result;
|
|
2147
2165
|
}
|
|
2166
|
+
/**
|
|
2167
|
+
* `markSuperseded`, tolerant of the two ways it legitimately doesn't land:
|
|
2168
|
+
* a missing target (a broken link, legal per compose.ts) or a CAS conflict
|
|
2169
|
+
* from a concurrent writer touching the same target. A conflict is retried
|
|
2170
|
+
* a bounded number of times — each attempt re-reads the target fresh — and
|
|
2171
|
+
* on the last, `false` reports "not marked" rather than throwing: the
|
|
2172
|
+
* caller's own record is already published, so failing here would leave
|
|
2173
|
+
* that publish unreported instead of undone. kb_validate's existing
|
|
2174
|
+
* "not marked superseded" check is what surfaces the residue.
|
|
2175
|
+
*/
|
|
2176
|
+
async markSupersededRetrying(bundlePath2, conceptId2, replacementId, actor, retries = 3) {
|
|
2177
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
2178
|
+
try {
|
|
2179
|
+
await this.markSuperseded(bundlePath2, conceptId2, replacementId, actor);
|
|
2180
|
+
return true;
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
if (error instanceof KbRecordNotFoundError) return false;
|
|
2183
|
+
if (!(error instanceof KbWriteConflictError)) throw error;
|
|
2184
|
+
if (attempt === retries) return false;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return false;
|
|
2188
|
+
}
|
|
2189
|
+
/** The one-directional half of `supersede`: marks `conceptId` superseded. */
|
|
2190
|
+
async markSuperseded(bundlePath2, conceptId2, replacementId, actor) {
|
|
2191
|
+
return this.mutate(
|
|
2192
|
+
bundlePath2,
|
|
2193
|
+
conceptId2,
|
|
2194
|
+
(frontmatter) => ({
|
|
2195
|
+
...frontmatter,
|
|
2196
|
+
strauss_status: "superseded",
|
|
2197
|
+
strauss_superseded_by: replacementId
|
|
2198
|
+
}),
|
|
2199
|
+
{ operation: "supersede", by: actor, target: replacementId }
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2148
2202
|
async mutate(bundlePath2, conceptId2, change, entry, changeBody = (body) => body) {
|
|
2149
2203
|
const target = this.recordPath(bundlePath2, conceptId2);
|
|
2150
2204
|
const before = await (0, import_promises4.readFile)(target, "utf8").catch(() => null);
|