@mrciphersmith/keryx 0.3.28 → 0.3.30
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/dist/core.js
CHANGED
|
@@ -21627,6 +21627,7 @@ init_validator();
|
|
|
21627
21627
|
var exports_service2 = {};
|
|
21628
21628
|
__export(exports_service2, {
|
|
21629
21629
|
AC_CHECK_TOKEN_BUDGET: () => AC_CHECK_TOKEN_BUDGET,
|
|
21630
|
+
AC_KINDS: () => AC_KINDS,
|
|
21630
21631
|
CONFIRMATION_CAVEAT: () => CONFIRMATION_CAVEAT,
|
|
21631
21632
|
REVIEW_GATE_CONFIG_PATH: () => REVIEW_GATE_CONFIG_PATH,
|
|
21632
21633
|
acCheckCacheKey: () => acCheckCacheKey,
|
|
@@ -21636,10 +21637,13 @@ __export(exports_service2, {
|
|
|
21636
21637
|
assertAcIntact: () => assertAcIntact,
|
|
21637
21638
|
baseBranchCondition: () => baseBranchCondition,
|
|
21638
21639
|
batchAcCheckItems: () => batchAcCheckItems,
|
|
21640
|
+
buildAcKindReport: () => buildAcKindReport,
|
|
21641
|
+
classifyAcCriterionNotCheckable: () => classifyAcCriterionNotCheckable,
|
|
21639
21642
|
classifyNotCheckable: () => classifyNotCheckable,
|
|
21640
21643
|
computeAcFacts: () => computeAcFacts,
|
|
21641
21644
|
confirmPreconditionError: () => confirmPreconditionError,
|
|
21642
21645
|
createFlowService: () => createFlowService,
|
|
21646
|
+
describeAcKind: () => describeAcKind,
|
|
21643
21647
|
evaluatedVerdict: () => evaluatedVerdict,
|
|
21644
21648
|
extractCriterionTokens: () => extractCriterionTokens,
|
|
21645
21649
|
factsOnlyVerdict: () => factsOnlyVerdict,
|
|
@@ -21649,12 +21653,16 @@ __export(exports_service2, {
|
|
|
21649
21653
|
parseAcceptanceCriteria: () => parseAcceptanceCriteria,
|
|
21650
21654
|
readAcCheckCache: () => readAcCheckCache,
|
|
21651
21655
|
readAcCheckEnabled: () => readAcCheckEnabled,
|
|
21656
|
+
readAcKindRecords: () => readAcKindRecords,
|
|
21652
21657
|
readFlow: () => readFlow,
|
|
21653
21658
|
renderAcCheckAdvisoryNotice: () => renderAcCheckAdvisoryNotice,
|
|
21654
21659
|
renderAcCheckReport: () => renderAcCheckReport,
|
|
21660
|
+
renderAcKindDistribution: () => renderAcKindDistribution,
|
|
21661
|
+
reportFromRecords: () => reportFromRecords,
|
|
21655
21662
|
resolveFlowDir: () => resolveFlowDir,
|
|
21656
21663
|
selectMatchedHunks: () => selectMatchedHunks,
|
|
21657
21664
|
statusLabel: () => statusLabel,
|
|
21665
|
+
stripVerifyMarker: () => stripVerifyMarker,
|
|
21658
21666
|
summarizeVerdicts: () => summarizeVerdicts,
|
|
21659
21667
|
validateCriterionName: () => validateCriterionName,
|
|
21660
21668
|
writeAcCheckCache: () => writeAcCheckCache
|
|
@@ -23979,6 +23987,161 @@ async function readRequireConfirmationDefault(cwd) {
|
|
|
23979
23987
|
return false;
|
|
23980
23988
|
}
|
|
23981
23989
|
}
|
|
23990
|
+
// src/flow/ac-kinds.ts
|
|
23991
|
+
var AC_KINDS = ["exec", "invariant", "judged", "none", "unclassified"];
|
|
23992
|
+
var AC_LINE = /^\s*[-*]\s*(AC\d+)\s*:\s?(.*)$/i;
|
|
23993
|
+
var MARKER_OPEN = "[verify:";
|
|
23994
|
+
function maskCodeSpans(text) {
|
|
23995
|
+
return text.replace(/`[^`]*`/g, (span) => " ".repeat(span.length));
|
|
23996
|
+
}
|
|
23997
|
+
function scanMarkers(text) {
|
|
23998
|
+
const masked = maskCodeSpans(text);
|
|
23999
|
+
let occurrences = 0;
|
|
24000
|
+
let lastStart = -1;
|
|
24001
|
+
for (let at = masked.indexOf(MARKER_OPEN);at !== -1; at = masked.indexOf(MARKER_OPEN, at + MARKER_OPEN.length)) {
|
|
24002
|
+
occurrences += 1;
|
|
24003
|
+
lastStart = at;
|
|
24004
|
+
}
|
|
24005
|
+
const end = text.trimEnd().length;
|
|
24006
|
+
return { occurrences, lastStart, trailing: occurrences > 0 && end > lastStart && text[end - 1] === "]", end };
|
|
24007
|
+
}
|
|
24008
|
+
function stripVerifyMarker(text) {
|
|
24009
|
+
const scan = scanMarkers(text);
|
|
24010
|
+
if (!scan.trailing || scan.occurrences !== 1)
|
|
24011
|
+
return text;
|
|
24012
|
+
return text.slice(0, scan.lastStart).trimEnd();
|
|
24013
|
+
}
|
|
24014
|
+
var KIND_WORDS = "exec, invariant, judged or none";
|
|
24015
|
+
function unclassified(error) {
|
|
24016
|
+
return error === undefined ? { record: { kind: "unclassified" } } : { record: { kind: "unclassified" }, error };
|
|
24017
|
+
}
|
|
24018
|
+
function parseAcKindText(text) {
|
|
24019
|
+
const scan = scanMarkers(text);
|
|
24020
|
+
if (scan.occurrences === 0)
|
|
24021
|
+
return unclassified();
|
|
24022
|
+
if (scan.occurrences > 1) {
|
|
24023
|
+
return unclassified("a second [verify: \u2026] marker \u2014 a criterion carries exactly one, as the last thing on its line");
|
|
24024
|
+
}
|
|
24025
|
+
if (!scan.trailing) {
|
|
24026
|
+
return unclassified("the [verify: \u2026] marker must be the last thing on the line and close with `]`");
|
|
24027
|
+
}
|
|
24028
|
+
const payload = text.slice(scan.lastStart + MARKER_OPEN.length, scan.end - 1).trim();
|
|
24029
|
+
if (payload.length === 0)
|
|
24030
|
+
return unclassified(`an empty [verify: \u2026] marker \u2014 expected ${KIND_WORDS}`);
|
|
24031
|
+
const word = /^[A-Za-z]+/.exec(payload)?.[0] ?? "";
|
|
24032
|
+
const rest = payload.slice(word.length).trim();
|
|
24033
|
+
switch (word) {
|
|
24034
|
+
case "judged":
|
|
24035
|
+
return rest.length === 0 ? { record: { kind: "judged" } } : unclassified("`judged` takes no arguments \u2014 write [verify: judged]");
|
|
24036
|
+
case "exec":
|
|
24037
|
+
case "invariant": {
|
|
24038
|
+
if (rest.length === 0)
|
|
24039
|
+
return unclassified(`\`${word}\` requires a backticked command, e.g. [verify: ${word} \`bun test path/to.test.ts\`]`);
|
|
24040
|
+
const command = /^`([^`]*)`$/.exec(rest);
|
|
24041
|
+
if (command === null)
|
|
24042
|
+
return unclassified(`\`${word}\` requires exactly one backticked command after the kind, e.g. [verify: ${word} \`bun test path/to.test.ts\`]`);
|
|
24043
|
+
const check = (command[1] ?? "").trim();
|
|
24044
|
+
if (check.length === 0)
|
|
24045
|
+
return unclassified(`\`${word}\` has an empty backticked command`);
|
|
24046
|
+
return { record: { kind: word, check } };
|
|
24047
|
+
}
|
|
24048
|
+
case "none": {
|
|
24049
|
+
if (rest.length === 0)
|
|
24050
|
+
return unclassified("`none` requires a reason after an em dash, e.g. [verify: none \u2014 <reason>]");
|
|
24051
|
+
const reason = /^\u2014\s*([\s\S]*)$/.exec(rest);
|
|
24052
|
+
if (reason === null)
|
|
24053
|
+
return unclassified("`none` requires its reason after an em dash (\u2014), e.g. [verify: none \u2014 <reason>]");
|
|
24054
|
+
const why = (reason[1] ?? "").trim();
|
|
24055
|
+
if (why.length === 0)
|
|
24056
|
+
return unclassified("`none` has an empty reason after the em dash");
|
|
24057
|
+
return { record: { kind: "none", reason: why } };
|
|
24058
|
+
}
|
|
24059
|
+
default:
|
|
24060
|
+
return unclassified(`unknown verification kind "${word.length > 0 ? word : payload}" \u2014 expected ${KIND_WORDS}`);
|
|
24061
|
+
}
|
|
24062
|
+
}
|
|
24063
|
+
function parseAcKinds(content) {
|
|
24064
|
+
const criteria = [];
|
|
24065
|
+
const errors = [];
|
|
24066
|
+
for (const line of content.split(/\r?\n/)) {
|
|
24067
|
+
const match = AC_LINE.exec(line);
|
|
24068
|
+
if (!match?.[1])
|
|
24069
|
+
continue;
|
|
24070
|
+
const id = match[1].toUpperCase();
|
|
24071
|
+
const rawText = (match[2] ?? "").trim();
|
|
24072
|
+
const parsed = parseAcKindText(rawText);
|
|
24073
|
+
if (parsed.error !== undefined)
|
|
24074
|
+
errors.push({ id, message: parsed.error });
|
|
24075
|
+
criteria.push({ id, text: stripVerifyMarker(rawText), rawText, record: parsed.record });
|
|
24076
|
+
}
|
|
24077
|
+
return { criteria, errors };
|
|
24078
|
+
}
|
|
24079
|
+
function emptyCounts() {
|
|
24080
|
+
return { exec: 0, invariant: 0, judged: 0, none: 0, unclassified: 0 };
|
|
24081
|
+
}
|
|
24082
|
+
function buildAcKindReport(flowId, content) {
|
|
24083
|
+
const parsed = parseAcKinds(content);
|
|
24084
|
+
const criteria = {};
|
|
24085
|
+
for (const criterion of parsed.criteria) {
|
|
24086
|
+
if (!(criterion.id in criteria))
|
|
24087
|
+
criteria[criterion.id] = criterion.record;
|
|
24088
|
+
}
|
|
24089
|
+
return { report: reportFromRecords(flowId, criteria), errors: parsed.errors };
|
|
24090
|
+
}
|
|
24091
|
+
function reportFromRecords(flowId, criteria) {
|
|
24092
|
+
const counts = emptyCounts();
|
|
24093
|
+
for (const record of Object.values(criteria))
|
|
24094
|
+
counts[record.kind] += 1;
|
|
24095
|
+
return {
|
|
24096
|
+
flowId,
|
|
24097
|
+
criteria,
|
|
24098
|
+
counts,
|
|
24099
|
+
runnable: counts.exec + counts.invariant,
|
|
24100
|
+
total: Object.keys(criteria).length
|
|
24101
|
+
};
|
|
24102
|
+
}
|
|
24103
|
+
function renderAcKindDistribution(report) {
|
|
24104
|
+
const { counts } = report;
|
|
24105
|
+
const percent = report.total === 0 ? 0 : Math.round(report.runnable / report.total * 100);
|
|
24106
|
+
const tail = [`${counts.none} accepted as unverifiable`, `${counts.judged} judged`, ...counts.unclassified > 0 ? [`${counts.unclassified} unclassified`] : []];
|
|
24107
|
+
return [
|
|
24108
|
+
`acceptance kinds: exec ${counts.exec} invariant ${counts.invariant} judged ${counts.judged} none ${counts.none} unclassified ${counts.unclassified} (${report.total} criteria)`,
|
|
24109
|
+
`coverage: ${report.runnable}/${report.total} runnable (${percent}%) \xB7 ${tail.join(" \xB7 ")}`
|
|
24110
|
+
];
|
|
24111
|
+
}
|
|
24112
|
+
function describeAcKind(record) {
|
|
24113
|
+
switch (record.kind) {
|
|
24114
|
+
case "exec":
|
|
24115
|
+
case "invariant":
|
|
24116
|
+
return `${record.kind} \`${record.check}\``;
|
|
24117
|
+
case "none":
|
|
24118
|
+
return `none \u2014 ${record.reason}`;
|
|
24119
|
+
default:
|
|
24120
|
+
return record.kind;
|
|
24121
|
+
}
|
|
24122
|
+
}
|
|
24123
|
+
function readAcKindRecords(value) {
|
|
24124
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
24125
|
+
return;
|
|
24126
|
+
const out = {};
|
|
24127
|
+
for (const [id, raw] of Object.entries(value)) {
|
|
24128
|
+
if (typeof raw !== "object" || raw === null)
|
|
24129
|
+
return;
|
|
24130
|
+
const record = raw;
|
|
24131
|
+
const kind = record["kind"];
|
|
24132
|
+
if ((kind === "exec" || kind === "invariant") && typeof record["check"] === "string")
|
|
24133
|
+
out[id] = { kind, check: record["check"] };
|
|
24134
|
+
else if (kind === "judged")
|
|
24135
|
+
out[id] = { kind };
|
|
24136
|
+
else if (kind === "none" && typeof record["reason"] === "string")
|
|
24137
|
+
out[id] = { kind, reason: record["reason"] };
|
|
24138
|
+
else if (kind === "unclassified")
|
|
24139
|
+
out[id] = { kind };
|
|
24140
|
+
else
|
|
24141
|
+
return;
|
|
24142
|
+
}
|
|
24143
|
+
return out;
|
|
24144
|
+
}
|
|
23982
24145
|
// src/flow/check-ac.ts
|
|
23983
24146
|
import { createHash as createHash8 } from "crypto";
|
|
23984
24147
|
import { chmod as chmod4, mkdir as mkdir9, readFile as readFile21 } from "fs/promises";
|
|
@@ -23992,11 +24155,11 @@ function estimateTokens(text) {
|
|
|
23992
24155
|
// src/flow/check-ac.ts
|
|
23993
24156
|
init_service2();
|
|
23994
24157
|
init_fs();
|
|
23995
|
-
var
|
|
24158
|
+
var AC_LINE2 = /^\s*[-*]\s*(AC\d+)\s*:\s?(.*)$/i;
|
|
23996
24159
|
function parseAcceptanceCriteria(content) {
|
|
23997
24160
|
const out = [];
|
|
23998
24161
|
for (const line of content.split(/\r?\n/)) {
|
|
23999
|
-
const match =
|
|
24162
|
+
const match = AC_LINE2.exec(line);
|
|
24000
24163
|
if (match?.[1]) {
|
|
24001
24164
|
out.push({ id: match[1].toUpperCase(), text: (match[2] ?? "").trim() });
|
|
24002
24165
|
}
|
|
@@ -24043,6 +24206,9 @@ function classifyNotCheckable(criterionText) {
|
|
|
24043
24206
|
reason: `not-checkable: every clause of this criterion names a condition no diff records (${labels.join(", ")}) \u2014 assigned without asking Jev.`
|
|
24044
24207
|
};
|
|
24045
24208
|
}
|
|
24209
|
+
function classifyAcCriterionNotCheckable(criterion) {
|
|
24210
|
+
return classifyNotCheckable(stripVerifyMarker(criterion.text));
|
|
24211
|
+
}
|
|
24046
24212
|
function extractCriterionTokens(text) {
|
|
24047
24213
|
const tokens = new Set;
|
|
24048
24214
|
for (const match of text.matchAll(/`([^`]+)`/g)) {
|
|
@@ -24060,7 +24226,7 @@ function extractCriterionTokens(text) {
|
|
|
24060
24226
|
return [...tokens];
|
|
24061
24227
|
}
|
|
24062
24228
|
function computeAcFacts(criterion, diffText, changedFiles) {
|
|
24063
|
-
const tokens = extractCriterionTokens(criterion.text);
|
|
24229
|
+
const tokens = extractCriterionTokens(stripVerifyMarker(criterion.text));
|
|
24064
24230
|
const tokensPresent = tokens.filter((token) => diffText.includes(token) || changedFiles.some((file) => file.includes(token)));
|
|
24065
24231
|
const tokensAbsent = tokens.filter((token) => !tokensPresent.includes(token));
|
|
24066
24232
|
const testFiles = changedFiles.filter((file) => /\.(test|spec)\.[a-z]+$/i.test(file));
|
|
@@ -24086,7 +24252,7 @@ var AC_CHECK_TOKEN_BUDGET = 64000;
|
|
|
24086
24252
|
function questionFor(criterion) {
|
|
24087
24253
|
return {
|
|
24088
24254
|
type: "noul",
|
|
24089
|
-
instructions: `Given the deterministic facts and the redacted diff evidence above, does this change LIKELY SATISFY this frozen ` + `acceptance criterion? Criterion ${criterion.id}: ${redactSensitiveText(criterion.text)}`
|
|
24255
|
+
instructions: `Given the deterministic facts and the redacted diff evidence above, does this change LIKELY SATISFY this frozen ` + `acceptance criterion? Criterion ${criterion.id}: ${redactSensitiveText(stripVerifyMarker(criterion.text))}`
|
|
24090
24256
|
};
|
|
24091
24257
|
}
|
|
24092
24258
|
var MAX_MATCHED_HUNKS_PER_ITEM = 8;
|
|
@@ -24431,6 +24597,10 @@ function renderJournal(createdAt) {
|
|
|
24431
24597
|
}
|
|
24432
24598
|
|
|
24433
24599
|
// src/flow/service.ts
|
|
24600
|
+
async function deriveAcKinds(cwd, dir, flowId) {
|
|
24601
|
+
const { report } = buildAcKindReport(flowId, await readFile23(acPath(cwd, dir), "utf8"));
|
|
24602
|
+
return { ...report.criteria };
|
|
24603
|
+
}
|
|
24434
24604
|
function recordAttempt(task, outcome, at, detail) {
|
|
24435
24605
|
const attempts = task.attempts ?? { count: 0, log: [] };
|
|
24436
24606
|
attempts.count += 1;
|
|
@@ -24628,6 +24798,7 @@ function createFlowService(deps) {
|
|
|
24628
24798
|
throw new Error("Cannot freeze: acceptance-criteria.md must contain at least one real `- ACn:` criterion.");
|
|
24629
24799
|
}
|
|
24630
24800
|
flow.acChecksum = await acChecksum(cwd, dir);
|
|
24801
|
+
flow.acKinds = await deriveAcKinds(cwd, dir, flow.id);
|
|
24631
24802
|
flow.status = "ready";
|
|
24632
24803
|
return save(cwd, dir, flow, "frozen", `${criteria.length} criteria; checksum recorded`);
|
|
24633
24804
|
});
|
|
@@ -24772,10 +24943,15 @@ function createFlowService(deps) {
|
|
|
24772
24943
|
detail = `${normalizedCriterion}: "${previousText ?? "(new)"}" -> "${normalizedText}" (${reason.trim()})`;
|
|
24773
24944
|
}
|
|
24774
24945
|
flow.acChecksum = await acChecksum(cwd, dir);
|
|
24946
|
+
flow.acKinds = await deriveAcKinds(cwd, dir, flow.id);
|
|
24775
24947
|
flow.acConfirmed = {};
|
|
24776
24948
|
return save(cwd, dir, flow, "ac-updated", detail);
|
|
24777
24949
|
});
|
|
24778
24950
|
},
|
|
24951
|
+
async acKinds({ cwd, id }) {
|
|
24952
|
+
const { dir, flow } = await load(cwd, id);
|
|
24953
|
+
return buildAcKindReport(flow.id, await readFile23(acPath(cwd, dir), "utf8"));
|
|
24954
|
+
},
|
|
24779
24955
|
async acReseal({ cwd, id, reason }) {
|
|
24780
24956
|
if (!reason?.trim()) {
|
|
24781
24957
|
throw new Error('flow ac reseal requires --reason "<why the checksum is stale>"');
|
|
@@ -24794,6 +24970,7 @@ function createFlowService(deps) {
|
|
|
24794
24970
|
throw new Error(`flow ac reseal: refusing \u2014 ${evidence.reason}. ` + "Reseal only re-seals a stale checksum over an unchanged file; it is not a way to " + 'accept an edit. If the criteria really changed, use `keryx flow ac update <id> --reason "..."`, ' + "which re-seals AND voids the prior confirmations.");
|
|
24795
24971
|
}
|
|
24796
24972
|
flow.acChecksum = current;
|
|
24973
|
+
flow.acKinds = await deriveAcKinds(cwd, dir, flow.id);
|
|
24797
24974
|
return save(cwd, dir, flow, "ac-resealed", reason);
|
|
24798
24975
|
});
|
|
24799
24976
|
},
|
|
@@ -37931,6 +38108,12 @@ var HELP_GROUPS = [
|
|
|
37931
38108
|
group: "Automation",
|
|
37932
38109
|
summary: "Read-only report over already-recorded spend, confirmations, signatures and gate outcomes."
|
|
37933
38110
|
},
|
|
38111
|
+
{
|
|
38112
|
+
kind: "cli",
|
|
38113
|
+
name: "product",
|
|
38114
|
+
group: "Automation",
|
|
38115
|
+
summary: "The product's intent as a derived index, and the intents closed in code that nobody looked back at."
|
|
38116
|
+
},
|
|
37934
38117
|
{
|
|
37935
38118
|
kind: "cli",
|
|
37936
38119
|
name: "hooks",
|
|
@@ -37967,6 +38150,12 @@ var HELP_GROUPS = [
|
|
|
37967
38150
|
group: "Automation",
|
|
37968
38151
|
summary: "Show the last governance report, or run one in the background."
|
|
37969
38152
|
},
|
|
38153
|
+
{
|
|
38154
|
+
kind: "slash",
|
|
38155
|
+
name: "/product",
|
|
38156
|
+
group: "Automation",
|
|
38157
|
+
summary: "Intents closed in code that nobody looked back at, with their outcome criteria."
|
|
38158
|
+
},
|
|
37970
38159
|
{
|
|
37971
38160
|
kind: "cli",
|
|
37972
38161
|
name: "acp",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.30",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"typecheck": "tsc --noEmit",
|
|
46
46
|
"typecheck:scripts": "tsc --project tsconfig.scripts.json --noEmit",
|
|
47
47
|
"test": "bun test",
|
|
48
|
-
"test:core": "bun test src/cli src/core src/impact-evidence/ src/shell-source-audits.test.ts src/acp/ src/assets/ src/bundle/ src/capability/ src/commands/ src/contracts/ src/ctx/ src/eval/ src/flow/ src/forgetting/ src/gdgraph/ src/gdskills/ src/governance/ src/health/ src/integrations/ src/job/ src/learning/ src/lib/ src/mcp/ src/memory/ src/metrics/ src/retention/ src/review/ src/rules/ src/sac/ src/security/ src/stack/ src/standard/ src/sync/ src/testing/ src/trigger/ src/wiki/ scripts/",
|
|
48
|
+
"test:core": "bun test src/cli src/core src/impact-evidence/ src/shell-source-audits.test.ts src/acp/ src/assets/ src/bundle/ src/capability/ src/commands/ src/contracts/ src/ctx/ src/eval/ src/flow/ src/forgetting/ src/gdgraph/ src/gdskills/ src/governance/ src/product/ src/health/ src/integrations/ src/job/ src/learning/ src/lib/ src/mcp/ src/memory/ src/metrics/ src/retention/ src/review/ src/rules/ src/sac/ src/security/ src/stack/ src/standard/ src/sync/ src/testing/ src/trigger/ src/wiki/ scripts/",
|
|
49
49
|
"test:client:terminal": "bun test src/tui/ src/commands/shell",
|
|
50
50
|
"test:client:streaming": "bun test src/harness/provider/",
|
|
51
51
|
"test:client:cancel-resume": "bun test src/harness/run/ src/harness/resume/ src/harness/session/ src/session/ src/bus/ src/commands/sessions",
|
|
@@ -62,11 +62,26 @@ document changes.
|
|
|
62
62
|
- problem;
|
|
63
63
|
- goal;
|
|
64
64
|
- users;
|
|
65
|
-
- requirements;
|
|
66
|
-
- success criteria;
|
|
65
|
+
- requirements, each carrying a verification field (see Verification Field below);
|
|
66
|
+
- success criteria, split into two lists (see Success Contract below);
|
|
67
67
|
- risks;
|
|
68
68
|
- recommendation.
|
|
69
69
|
|
|
70
|
+
### Success Contract
|
|
71
|
+
|
|
72
|
+
The PRD's success criteria are two lists, never one:
|
|
73
|
+
|
|
74
|
+
- **Release criteria** are checkable when the work ships: a test, a command, a
|
|
75
|
+
file, a count. Each is something a release can be blocked on.
|
|
76
|
+
- **Outcome criteria** are what should be true some time after release: adoption,
|
|
77
|
+
a measured change, a behavior seen in use. Each outcome criterion either names
|
|
78
|
+
the observation that would confirm it (what is looked at, where, and when) or
|
|
79
|
+
declares `not measured — <reason>`.
|
|
80
|
+
|
|
81
|
+
An outcome criterion with neither an observation nor a `not measured — <reason>`
|
|
82
|
+
is not a criterion. A list whose every entry is `not measured` is acceptable: it
|
|
83
|
+
is honest. A PRD with no outcome list at all is not.
|
|
84
|
+
|
|
70
85
|
## Specification Contract
|
|
71
86
|
|
|
72
87
|
`specification.md` must state:
|
|
@@ -79,6 +94,21 @@ document changes.
|
|
|
79
94
|
- integration points;
|
|
80
95
|
- acceptance criteria.
|
|
81
96
|
|
|
97
|
+
### Verification Field
|
|
98
|
+
|
|
99
|
+
Every requirement in the PRD (each `R<n>` under Requirements) carries one verification field, in the
|
|
100
|
+
same vocabulary a flow criterion's trailing marker uses:
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
Verification: exec `<command>` # a command whose exit code decides it
|
|
104
|
+
Verification: invariant `<command>` # a command that must keep passing
|
|
105
|
+
Verification: judged # a person reads the evidence
|
|
106
|
+
Verification: none — <reason> # honestly unverifiable, with the reason
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`exec` and `invariant` name a backticked command; `none` names its reason after
|
|
110
|
+
an em dash. A requirement with no field is a defect, not a `none`.
|
|
111
|
+
|
|
82
112
|
## Verification Contract
|
|
83
113
|
|
|
84
114
|
Before finishing:
|
|
@@ -88,6 +118,11 @@ Before finishing:
|
|
|
88
118
|
- verify README links to all package files;
|
|
89
119
|
- verify schemas are referenced from specification when present;
|
|
90
120
|
- verify status is honest: `draft`, `spec ready`, `implemented`, or `future`;
|
|
121
|
+
- verify every PRD requirement carries a `Verification:` field, and
|
|
122
|
+
fail the package when one does not, naming that requirement;
|
|
123
|
+
- verify the PRD has both a release-criteria list and an outcome-criteria list,
|
|
124
|
+
and fail the package when the outcome list is missing (an outcome list whose
|
|
125
|
+
entries are all `not measured — <reason>` passes);
|
|
91
126
|
- update `docs/requirements/roadmap.md` when the package represents a module or
|
|
92
127
|
standard capability.
|
|
93
128
|
|
|
@@ -109,10 +109,8 @@ Add optional files only when justified:
|
|
|
109
109
|
Write concise Markdown with explicit headings. Required contracts:
|
|
110
110
|
|
|
111
111
|
- README: purpose, status, document index, scope, related modules.
|
|
112
|
-
- PRD: problem, goal, users, requirements, success criteria, risks,
|
|
113
|
-
|
|
114
|
-
- Specification: identity, structure, manifest/config, CLI/skill surface, data
|
|
115
|
-
contracts, integrations, acceptance criteria.
|
|
112
|
+
- PRD: problem, goal, users, requirements (each `R<n>` carries a `Verification:` field: `exec`/`invariant` with a backticked command, `judged`, or `none — <reason>`), success criteria (a release list and an outcome list; an outcome criterion names its observation or declares `not measured — <reason>`), risks, recommendation.
|
|
113
|
+
- Specification: identity, structure, manifest/config, CLI/skill surface, data contracts, integrations, acceptance criteria.
|
|
116
114
|
|
|
117
115
|
When updating existing docs, preserve useful content and bump versions.
|
|
118
116
|
|
|
@@ -125,6 +123,7 @@ Run a local verification pass:
|
|
|
125
123
|
- README links to every package file;
|
|
126
124
|
- schema files are valid JSON;
|
|
127
125
|
- specification references schemas when present;
|
|
126
|
+
- every PRD requirement has a `Verification:` field (a package with a requirement that omits it FAILS, naming that requirement); the PRD has a release list and an outcome list (no outcome list FAILS; an outcome list whose entries are all `not measured — <reason>` passes);
|
|
128
127
|
- roadmap is updated for new module/standard capabilities;
|
|
129
128
|
- no implementation status is overstated.
|
|
130
129
|
|
|
@@ -154,6 +153,7 @@ Do not emit the Final Response until all of these hold:
|
|
|
154
153
|
- Every Markdown file in the package carries a `Version`, and every file you changed has a bumped one.
|
|
155
154
|
- `README.md` links to every file in the package; the specification links to each schema it defines.
|
|
156
155
|
- Every `schemas/*.json` parses as valid JSON.
|
|
156
|
+
- Every specification requirement carries a `Verification:` field, and the PRD carries both a release list and an outcome list; a package missing either was reported as `verification: fail`, naming the requirement.
|
|
157
157
|
- `docs/requirements/roadmap.md` is updated when the package represents a new module or capability, or the report states why it does not.
|
|
158
158
|
- No implementation claim appears that code does not support; anything unbuilt is marked planned or future.
|
|
159
159
|
- `docpack-review` has been run in Phase 5 and reports zero blockers. Remaining warnings appear verbatim in `remaining_gaps`.
|