frontend-harness 0.8.3 → 0.8.5
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 +3 -3
- package/dist/cli/index.js +20 -19
- package/dist/cli/index.js.map +1 -1
- package/dist/runtime/builtin-skills.js +2 -2
- package/dist/runtime/knowledge/core.js +84 -145
- package/dist/runtime/knowledge/core.js.map +1 -1
- package/dist/runtime/knowledge/types.d.ts +2 -5
- package/dist/runtime/plan/workflow.js +4 -4
- package/dist/runtime/plan/workflow.js.map +1 -1
- package/dist/runtime/plan.js +2 -2
- package/dist/runtime/plan.js.map +1 -1
- package/dist/runtime/protocol-init.js +2 -2
- package/dist/runtime/protocol-init.js.map +1 -1
- package/dist/runtime/task-context.js +2 -1
- package/dist/runtime/task-context.js.map +1 -1
- package/dist/runtime/verify.js +4 -4
- package/dist/runtime/verify.js.map +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ export const MAX_KNOWLEDGE_DRAFT_EXTRACTION_ITEMS = 40;
|
|
|
13
13
|
export const MAX_KNOWLEDGE_INDEX_CARDS = 20;
|
|
14
14
|
export const MAX_KNOWLEDGE_INDEX_DETAIL_ITEMS = 20;
|
|
15
15
|
const MAX_KNOWLEDGE_REFERENCE_SUGGESTIONS = 20;
|
|
16
|
+
const MIN_SELF_CONTAINED_PRD_CONTENT_CHARS = 80;
|
|
16
17
|
export function promoteKnowledge(projectRoot, input) {
|
|
17
18
|
const title = input.title?.trim();
|
|
18
19
|
const body = input.body?.trim();
|
|
@@ -29,6 +30,8 @@ export function promoteKnowledge(projectRoot, input) {
|
|
|
29
30
|
const id = slugify(title) || `knowledge-${formatKnowledgeStamp(createdAt).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")}`;
|
|
30
31
|
const fileName = `${formatKnowledgeStamp(createdAt)}-${id}.md`;
|
|
31
32
|
const relativePath = relativeHarnessPath("knowledge", fileName);
|
|
33
|
+
const coverage = splitList(input.coverage);
|
|
34
|
+
const sourcePaths = parseKnowledgeSourcePaths(projectRoot, input.source, "knowledge promote --source");
|
|
32
35
|
const content = renderKnowledge({
|
|
33
36
|
id,
|
|
34
37
|
title,
|
|
@@ -40,9 +43,9 @@ export function promoteKnowledge(projectRoot, input) {
|
|
|
40
43
|
scope: splitList(input.scope),
|
|
41
44
|
tags: splitList(input.tags),
|
|
42
45
|
verification: splitList(input.verification),
|
|
43
|
-
coverage
|
|
46
|
+
coverage,
|
|
44
47
|
prdAnchors: splitList(input.anchor),
|
|
45
|
-
sourcePaths
|
|
48
|
+
sourcePaths
|
|
46
49
|
});
|
|
47
50
|
writeText(path.join(projectRoot, relativePath), content);
|
|
48
51
|
return {
|
|
@@ -75,6 +78,8 @@ export function createModuleKnowledge(projectRoot, input) {
|
|
|
75
78
|
if (!id || !/^[a-z0-9][a-z0-9-]{2,80}$/.test(id)) {
|
|
76
79
|
throw new Error("knowledge module --id must be kebab-case when provided");
|
|
77
80
|
}
|
|
81
|
+
const coverage = splitList(input.coverage);
|
|
82
|
+
const sourcePaths = parseKnowledgeSourcePaths(projectRoot, input.source, "knowledge module --source");
|
|
78
83
|
const relativePath = relativeHarnessPath("knowledge", "modules", `${id}.md`);
|
|
79
84
|
const fullPath = path.join(projectRoot, relativePath);
|
|
80
85
|
if (fs.existsSync(fullPath)) {
|
|
@@ -91,9 +96,9 @@ export function createModuleKnowledge(projectRoot, input) {
|
|
|
91
96
|
scope: splitList(input.scope),
|
|
92
97
|
tags: splitList(input.tags),
|
|
93
98
|
verification: splitList(input.verification),
|
|
94
|
-
coverage
|
|
99
|
+
coverage,
|
|
95
100
|
prdAnchors: splitList(input.anchor),
|
|
96
|
-
sourcePaths
|
|
101
|
+
sourcePaths,
|
|
97
102
|
related: splitList(input.related)
|
|
98
103
|
}, body);
|
|
99
104
|
writeText(fullPath, content);
|
|
@@ -133,8 +138,9 @@ export function addKnowledge(projectRoot, input) {
|
|
|
133
138
|
const contentFields = atomicContentFields(kind, input);
|
|
134
139
|
const inputSummary = input.summary?.trim();
|
|
135
140
|
const contentSummary = inputSummary || summarizeAtomicContent(kind, subject, contentFields);
|
|
136
|
-
const sourcePaths = parseKnowledgeSourcePaths(input.source, "knowledge add --source");
|
|
141
|
+
const sourcePaths = parseKnowledgeSourcePaths(projectRoot, input.source, "knowledge add --source");
|
|
137
142
|
const type = typeForAtomicKnowledge(kind, sourcePaths);
|
|
143
|
+
const coverage = splitList(input.coverage);
|
|
138
144
|
const rendered = renderAtomicKnowledge({
|
|
139
145
|
id,
|
|
140
146
|
title,
|
|
@@ -149,7 +155,7 @@ export function addKnowledge(projectRoot, input) {
|
|
|
149
155
|
tags: splitList(input.tags),
|
|
150
156
|
sourcePaths,
|
|
151
157
|
verification: splitList(input.verification),
|
|
152
|
-
coverage
|
|
158
|
+
coverage,
|
|
153
159
|
prdAnchors: splitList(input.anchor),
|
|
154
160
|
contentFields
|
|
155
161
|
});
|
|
@@ -627,7 +633,7 @@ export function updateKnowledge(projectRoot, input) {
|
|
|
627
633
|
verification: mergeList(metadata.verification, input.verification),
|
|
628
634
|
coverage: mergeList(metadata.coverage, input.coverage),
|
|
629
635
|
prdAnchors: mergeList(metadata.prdAnchors, input.anchor),
|
|
630
|
-
sourcePaths: mergeKnowledgeSourcePaths(metadata.sourcePaths, input.source, "knowledge update --source"),
|
|
636
|
+
sourcePaths: mergeKnowledgeSourcePaths(projectRoot, metadata.sourcePaths, input.source, "knowledge update --source"),
|
|
631
637
|
related: mergeList(metadata.related, input.related)
|
|
632
638
|
};
|
|
633
639
|
for (const key of ["rule", "workflow", "permission", "term", "note"]) {
|
|
@@ -700,63 +706,46 @@ export function checkKnowledgeCoverage(projectRoot, options = {}) {
|
|
|
700
706
|
prdSourceCount: 0,
|
|
701
707
|
coveredPrdSourceCount: 0,
|
|
702
708
|
uncoveredPrdSources: [],
|
|
703
|
-
prdSourcesWithoutCoverageItems: [],
|
|
704
709
|
missingSourcePaths: [],
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
cardsWithBroadCoverage: [],
|
|
708
|
-
cardsWithTraceabilityDrift: [],
|
|
710
|
+
cardsWithThinContent: [],
|
|
711
|
+
cardsWithPlaceholderContent: [],
|
|
709
712
|
warnings: ["Create .frontend-harness/knowledge before checking project knowledge coverage."],
|
|
710
713
|
errors: []
|
|
711
714
|
};
|
|
712
715
|
}
|
|
713
716
|
const prdSources = summarizePrdSources(projectRoot, cards, options.requiredPrdSources ?? []);
|
|
717
|
+
const activePrdKnowledgeCards = cards.filter((card) => card.status === "active" && isPrdKnowledgeQualityCard(card));
|
|
714
718
|
const uncoveredPrdSources = prdSources
|
|
715
719
|
.filter((source) => source.activeCardCount === 0)
|
|
716
720
|
.map((source) => source.path);
|
|
721
|
+
const requiredPrdKnowledgeMissing = (options.requiredPrdSources?.length ?? 0) > 0 && activePrdKnowledgeCards.length === 0
|
|
722
|
+
? (uncoveredPrdSources.length ? uncoveredPrdSources : options.requiredPrdSources ?? [])
|
|
723
|
+
: [];
|
|
717
724
|
const missingSourcePaths = [...new Set(cards.flatMap((card) => card.sourcePaths)
|
|
718
725
|
.filter((sourcePath) => sourcePath && !isExternalReference(sourcePath) && !isResolvedKnowledgeSource(projectRoot, sourcePath)))].sort();
|
|
719
|
-
const
|
|
720
|
-
.filter((card) => card.status === "active" && card
|
|
721
|
-
.map((card) => card.path);
|
|
722
|
-
const cardsWithoutCoverage = cards
|
|
723
|
-
.filter((card) => card.status === "active" && isPrdTraceableCard(card) && card.coverage.length === 0)
|
|
726
|
+
const cardsWithThinContent = cards
|
|
727
|
+
.filter((card) => card.status === "active" && isPrdKnowledgeQualityCard(card) && isThinSelfContainedKnowledge(card))
|
|
724
728
|
.map((card) => card.path);
|
|
725
|
-
const
|
|
726
|
-
.filter((card) => card.status === "active" &&
|
|
729
|
+
const cardsWithPlaceholderContent = cards
|
|
730
|
+
.filter((card) => card.status === "active" && isPrdKnowledgeQualityCard(card) && hasPlaceholderKnowledgeContent(card))
|
|
727
731
|
.map((card) => card.path);
|
|
728
|
-
const cardsWithTraceabilityDrift = cards
|
|
729
|
-
.filter((card) => card.status === "active" && isPrdTraceableCard(card) && hasCoverageBodyDrift(projectRoot, card))
|
|
730
|
-
.map((card) => card.path);
|
|
731
|
-
const prdSourcesWithoutCoverageItems = prdSources
|
|
732
|
-
.filter((source) => source.activeCardCount > 0 && source.coverage.length === 0)
|
|
733
|
-
.map((source) => source.path);
|
|
734
732
|
const warnings = [
|
|
735
|
-
...
|
|
736
|
-
...cardsWithoutCoverage.map((cardPath) => `${cardPath} has no coverage items for PRD block traceability.`),
|
|
737
|
-
...cardsWithBroadCoverage.map((cardPath) => `${cardPath} uses broad PRD coverage; split PRD semantics into atomic cards with precise section, table, rule, or acceptance-block coverage.`),
|
|
738
|
-
...cardsWithTraceabilityDrift.map((cardPath) => `${cardPath} frontmatter coverage disagrees with body traceability.`),
|
|
739
|
-
...prdSourcesWithoutCoverageItems.map((sourcePath) => `PRD source has cards but no coverage items: ${sourcePath}`),
|
|
733
|
+
...uncoveredPrdSources.map((sourcePath) => `PRD source has no active knowledge cards: ${sourcePath}`),
|
|
740
734
|
...missingSourcePaths.map((sourcePath) => `source path does not exist: ${sourcePath}`)
|
|
741
735
|
];
|
|
742
736
|
const errors = [
|
|
743
|
-
...
|
|
744
|
-
...
|
|
745
|
-
...
|
|
746
|
-
...cardsWithTraceabilityDrift.map((cardPath) => `${cardPath} has frontmatter coverage but body traceability says coverage is not specified.`),
|
|
747
|
-
...missingSourcePaths.map((sourcePath) => `source path does not exist: ${sourcePath}`)
|
|
737
|
+
...requiredPrdKnowledgeMissing.map((sourcePath) => `PRD input has no active self-contained knowledge cards: ${sourcePath}`),
|
|
738
|
+
...cardsWithThinContent.map((cardPath) => `${cardPath} is too thin to replace deleted PRD source material; keep self-contained implementation constraints.`),
|
|
739
|
+
...cardsWithPlaceholderContent.map((cardPath) => `${cardPath} still contains scaffold placeholder text instead of durable knowledge.`)
|
|
748
740
|
];
|
|
749
741
|
return {
|
|
750
|
-
status: errors.length
|
|
742
|
+
status: errors.length ? "failed" : "passed",
|
|
751
743
|
prdSourceCount: prdSources.length,
|
|
752
744
|
coveredPrdSourceCount: prdSources.filter((source) => source.activeCardCount > 0).length,
|
|
753
745
|
uncoveredPrdSources,
|
|
754
|
-
prdSourcesWithoutCoverageItems,
|
|
755
746
|
missingSourcePaths,
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
cardsWithBroadCoverage,
|
|
759
|
-
cardsWithTraceabilityDrift,
|
|
747
|
+
cardsWithThinContent,
|
|
748
|
+
cardsWithPlaceholderContent,
|
|
760
749
|
warnings,
|
|
761
750
|
errors
|
|
762
751
|
};
|
|
@@ -908,15 +897,6 @@ export function checkKnowledge(projectRoot) {
|
|
|
908
897
|
for (const verification of parsed.metadata.verification.filter(isDraftPlaceholderVerification)) {
|
|
909
898
|
errors.push(`${relativePath} verification contains an unedited knowledge draft placeholder: ${verification}.`);
|
|
910
899
|
}
|
|
911
|
-
if (parsed.metadata.sourcePaths.some(isPrdSourcePath) && parsed.metadata.coverage.length === 0) {
|
|
912
|
-
warnings.push(`${relativePath} should include coverage items for PRD block traceability.`);
|
|
913
|
-
}
|
|
914
|
-
if (parsed.metadata.sourcePaths.some(isPrdSourcePath) && parsed.metadata.coverage.some(isBroadCoverageItem)) {
|
|
915
|
-
warnings.push(`${relativePath} uses broad PRD coverage; split PRD semantics into smaller atomic cards with precise section, table, rule, or acceptance-block coverage.`);
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
if (parsed.metadata.coverage.length > 0 && /\bCoverage:\s*Not specified\./i.test(extractMarkdownBody(content))) {
|
|
919
|
-
warnings.push(`${relativePath} frontmatter coverage disagrees with body traceability.`);
|
|
920
900
|
}
|
|
921
901
|
for (const sourcePath of parsed.metadata.sourcePaths) {
|
|
922
902
|
if (sourcePath && !isExternalReference(sourcePath) && !isResolvedKnowledgeSource(projectRoot, sourcePath)) {
|
|
@@ -1177,44 +1157,23 @@ function validateKnowledgeMetadata(relativePath, metadata) {
|
|
|
1177
1157
|
return errors;
|
|
1178
1158
|
}
|
|
1179
1159
|
function renderKnowledgeFromMetadata(metadata, body) {
|
|
1180
|
-
const renderedBody =
|
|
1160
|
+
const renderedBody = renderBodyWithVerification(body, metadata.verification ?? []);
|
|
1181
1161
|
return `---
|
|
1182
1162
|
id: ${yamlScalar(metadata.id)}
|
|
1183
1163
|
type: ${yamlScalar(metadata.type)}
|
|
1184
1164
|
${renderOptionalScalar("kind", metadata.kind)}title: ${yamlScalar(metadata.title)}
|
|
1185
1165
|
summary: ${yamlScalar(metadata.summary)}
|
|
1186
|
-
${renderOptionalScalar("subject", metadata.subject)}${renderOptionalScalar("rule", metadata.rule)}${renderOptionalScalar("workflow", metadata.workflow)}${renderOptionalScalar("permission", metadata.permission)}${renderOptionalScalar("term", metadata.term)}${
|
|
1187
|
-
${renderList(metadata.scope)}
|
|
1188
|
-
tags:
|
|
1189
|
-
${renderList(metadata.tags)}
|
|
1190
|
-
verification:
|
|
1191
|
-
${renderList(metadata.verification)}
|
|
1192
|
-
coverage:
|
|
1193
|
-
${renderList(metadata.coverage ?? [])}
|
|
1194
|
-
prd_anchors:
|
|
1195
|
-
${renderList(metadata.prdAnchors ?? [])}
|
|
1196
|
-
status: ${yamlScalar(metadata.status)}
|
|
1166
|
+
${renderOptionalScalar("subject", metadata.subject)}${renderOptionalScalar("rule", metadata.rule)}${renderOptionalScalar("workflow", metadata.workflow)}${renderOptionalScalar("permission", metadata.permission)}${renderOptionalScalar("term", metadata.term)}${renderOptionalList("scope", metadata.scope)}${renderOptionalList("tags", metadata.tags)}${renderOptionalList("verification", metadata.verification)}${renderOptionalList("coverage", metadata.coverage ?? [])}${renderOptionalList("prd_anchors", metadata.prdAnchors ?? [])}status: ${yamlScalar(metadata.status)}
|
|
1197
1167
|
stability: ${yamlScalar(metadata.stability)}
|
|
1198
1168
|
updated_at: ${yamlScalar(metadata.updatedAt)}
|
|
1199
|
-
source_paths
|
|
1200
|
-
${renderList(metadata.sourcePaths)}
|
|
1201
|
-
related:
|
|
1202
|
-
${renderList(metadata.related)}
|
|
1169
|
+
${renderOptionalList("source_paths", metadata.sourcePaths)}${renderOptionalList("related", metadata.related)}
|
|
1203
1170
|
---
|
|
1204
1171
|
|
|
1205
1172
|
${renderedBody}
|
|
1206
1173
|
`;
|
|
1207
1174
|
}
|
|
1208
|
-
function
|
|
1175
|
+
function renderBodyWithVerification(body, verification) {
|
|
1209
1176
|
const sections = [body.trim()];
|
|
1210
|
-
if ((coverage.length || prdAnchors.length) && !/^## Traceability\b/m.test(body)) {
|
|
1211
|
-
sections.push([
|
|
1212
|
-
"## Traceability",
|
|
1213
|
-
"",
|
|
1214
|
-
...(coverage.length ? coverage.map((item) => `- Coverage: ${item}`) : ["- Coverage: Not specified."]),
|
|
1215
|
-
...(prdAnchors.length ? prdAnchors.map((item) => `- Anchor: ${item}`) : [])
|
|
1216
|
-
].join("\n"));
|
|
1217
|
-
}
|
|
1218
1177
|
if (verification.length && !/^## Verification\b/m.test(body)) {
|
|
1219
1178
|
sections.push([
|
|
1220
1179
|
"## Verification",
|
|
@@ -1232,33 +1191,24 @@ kind: ${yamlScalar(input.kind)}
|
|
|
1232
1191
|
title: ${yamlScalar(input.title)}
|
|
1233
1192
|
summary: ${yamlScalar(input.summary)}
|
|
1234
1193
|
subject: ${yamlScalar(input.subject)}
|
|
1235
|
-
${renderOptionalScalar("rule", input.contentFields.rule)}${renderOptionalScalar("workflow", input.contentFields.workflow)}${renderOptionalScalar("permission", input.contentFields.permission)}${renderOptionalScalar("term", input.contentFields.term)}${renderOptionalScalar("note", input.contentFields.note)}scope:
|
|
1236
|
-
${renderList(input.scope)}
|
|
1237
|
-
tags:
|
|
1238
|
-
${renderList(input.tags)}
|
|
1239
|
-
verification:
|
|
1240
|
-
${renderList(input.verification)}
|
|
1241
|
-
coverage:
|
|
1242
|
-
${renderList(input.coverage)}
|
|
1243
|
-
prd_anchors:
|
|
1244
|
-
${renderList(input.prdAnchors)}
|
|
1245
|
-
status: ${yamlScalar(input.status)}
|
|
1194
|
+
${renderOptionalScalar("rule", input.contentFields.rule)}${renderOptionalScalar("workflow", input.contentFields.workflow)}${renderOptionalScalar("permission", input.contentFields.permission)}${renderOptionalScalar("term", input.contentFields.term)}${renderOptionalScalar("note", input.contentFields.note)}${renderOptionalList("scope", input.scope)}${renderOptionalList("tags", input.tags)}${renderOptionalList("verification", input.verification)}${renderOptionalList("coverage", input.coverage)}${renderOptionalList("prd_anchors", input.prdAnchors)}status: ${yamlScalar(input.status)}
|
|
1246
1195
|
stability: ${yamlScalar(input.stability)}
|
|
1247
1196
|
updated_at: ${yamlScalar(input.createdAt)}
|
|
1248
|
-
source_paths
|
|
1249
|
-
${renderList(input.sourcePaths)}
|
|
1250
|
-
related: []
|
|
1197
|
+
${renderOptionalList("source_paths", input.sourcePaths)}
|
|
1251
1198
|
---
|
|
1252
1199
|
|
|
1253
1200
|
# ${input.title}
|
|
1254
1201
|
|
|
1255
|
-
${renderAtomicBody(input.kind, input.subject, input.summary, input.contentFields, input.
|
|
1202
|
+
${renderAtomicBody(input.kind, input.subject, input.summary, input.contentFields, input.verification)}
|
|
1256
1203
|
`;
|
|
1257
1204
|
}
|
|
1258
1205
|
function renderOptionalScalar(key, value) {
|
|
1259
1206
|
return value ? `${key}: ${yamlScalar(value)}\n` : "";
|
|
1260
1207
|
}
|
|
1261
|
-
function
|
|
1208
|
+
function renderOptionalList(key, items) {
|
|
1209
|
+
return items.length ? `${key}:\n${renderList(items)}\n` : "";
|
|
1210
|
+
}
|
|
1211
|
+
function renderAtomicBody(kind, subject, summary, contentFields, verification) {
|
|
1262
1212
|
const contentEntries = Object.entries(contentFields).filter(([, value]) => Boolean(value?.trim()));
|
|
1263
1213
|
const primaryLabel = titleCase(contentKeyForKind(kind));
|
|
1264
1214
|
const primaryValue = contentFields[contentKeyForKind(kind)] ?? contentEntries[0]?.[1];
|
|
@@ -1277,36 +1227,25 @@ function renderAtomicBody(kind, subject, summary, contentFields, coverage, prdAn
|
|
|
1277
1227
|
if (note && contentKeyForKind(kind) !== "note") {
|
|
1278
1228
|
sections.push("", "## Notes", "", note);
|
|
1279
1229
|
}
|
|
1280
|
-
|
|
1281
|
-
|
|
1230
|
+
if (verification.length) {
|
|
1231
|
+
sections.push("", "## Verification", "", ...verification.map((item) => `- ${item}`));
|
|
1232
|
+
}
|
|
1282
1233
|
return [
|
|
1283
1234
|
...sections
|
|
1284
1235
|
].join("\n");
|
|
1285
1236
|
}
|
|
1286
1237
|
function renderKnowledge(input) {
|
|
1287
1238
|
const summary = firstBodyLine(input.body) ?? input.title;
|
|
1288
|
-
const body =
|
|
1239
|
+
const body = renderBodyWithVerification(input.body, input.verification);
|
|
1289
1240
|
return `---
|
|
1290
1241
|
id: ${yamlScalar(input.id)}
|
|
1291
1242
|
type: ${yamlScalar(input.type)}
|
|
1292
1243
|
title: ${yamlScalar(input.title)}
|
|
1293
1244
|
summary: ${yamlScalar(summary)}
|
|
1294
|
-
scope:
|
|
1295
|
-
${renderList(input.scope)}
|
|
1296
|
-
tags:
|
|
1297
|
-
${renderList(input.tags)}
|
|
1298
|
-
verification:
|
|
1299
|
-
${renderList(input.verification)}
|
|
1300
|
-
coverage:
|
|
1301
|
-
${renderList(input.coverage)}
|
|
1302
|
-
prd_anchors:
|
|
1303
|
-
${renderList(input.prdAnchors)}
|
|
1304
|
-
status: ${yamlScalar(input.status)}
|
|
1245
|
+
${renderOptionalList("scope", input.scope)}${renderOptionalList("tags", input.tags)}${renderOptionalList("verification", input.verification)}${renderOptionalList("coverage", input.coverage)}${renderOptionalList("prd_anchors", input.prdAnchors)}status: ${yamlScalar(input.status)}
|
|
1305
1246
|
stability: ${yamlScalar(input.stability)}
|
|
1306
1247
|
updated_at: ${yamlScalar(input.createdAt)}
|
|
1307
|
-
source_paths
|
|
1308
|
-
${renderList(input.sourcePaths)}
|
|
1309
|
-
related: []
|
|
1248
|
+
${renderOptionalList("source_paths", input.sourcePaths)}
|
|
1310
1249
|
---
|
|
1311
1250
|
|
|
1312
1251
|
# ${input.title}
|
|
@@ -1398,25 +1337,35 @@ function splitList(value) {
|
|
|
1398
1337
|
? value.split(",").map((item) => item.trim()).filter(Boolean)
|
|
1399
1338
|
: [];
|
|
1400
1339
|
}
|
|
1401
|
-
function parseKnowledgeSourcePaths(value, label) {
|
|
1402
|
-
const sourcePaths = splitList(value);
|
|
1340
|
+
function parseKnowledgeSourcePaths(projectRoot, value, label) {
|
|
1341
|
+
const sourcePaths = canonicalKnowledgeInputSourcePaths(projectRoot, splitList(value));
|
|
1403
1342
|
const invalid = sourcePaths.find(validateKnowledgeSourcePath);
|
|
1404
1343
|
if (invalid) {
|
|
1405
1344
|
throw new Error(`${label} must contain safe project-relative paths or external references: ${invalid}`);
|
|
1406
1345
|
}
|
|
1407
1346
|
return sourcePaths;
|
|
1408
1347
|
}
|
|
1409
|
-
function mergeKnowledgeSourcePaths(existing, next, label) {
|
|
1348
|
+
function mergeKnowledgeSourcePaths(projectRoot, existing, next, label) {
|
|
1410
1349
|
if (next === undefined) {
|
|
1411
1350
|
return existing;
|
|
1412
1351
|
}
|
|
1413
|
-
const merged = [...new Set([...existing, ...splitList(next)])];
|
|
1352
|
+
const merged = [...new Set(canonicalKnowledgeInputSourcePaths(projectRoot, [...existing, ...splitList(next)]))];
|
|
1414
1353
|
const invalid = merged.find(validateKnowledgeSourcePath);
|
|
1415
1354
|
if (invalid) {
|
|
1416
1355
|
throw new Error(`${label} must contain safe project-relative paths or external references: ${invalid}`);
|
|
1417
1356
|
}
|
|
1418
1357
|
return merged;
|
|
1419
1358
|
}
|
|
1359
|
+
function canonicalKnowledgeInputSourcePaths(projectRoot, sourcePaths) {
|
|
1360
|
+
if (sourcePaths.length === 0) {
|
|
1361
|
+
return [];
|
|
1362
|
+
}
|
|
1363
|
+
const records = listIngestRecords(projectRoot, undefined, { limit: "all" }).records;
|
|
1364
|
+
return sourcePaths.map((sourcePath) => {
|
|
1365
|
+
const record = records.find((item) => sameKnowledgeSourceReference(item.ingestRef, sourcePath) || sameKnowledgeSourceReference(item.source.value, sourcePath));
|
|
1366
|
+
return record?.ingestRef ?? sourcePath;
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1420
1369
|
function validateKnowledgeSourcePath(value) {
|
|
1421
1370
|
if (!value.trim()) {
|
|
1422
1371
|
return "empty";
|
|
@@ -1664,26 +1613,6 @@ function parseIngestReference(value) {
|
|
|
1664
1613
|
id: match[2]
|
|
1665
1614
|
};
|
|
1666
1615
|
}
|
|
1667
|
-
function isBroadCoverageItem(value) {
|
|
1668
|
-
const normalized = value.trim().toLowerCase();
|
|
1669
|
-
if (!normalized) {
|
|
1670
|
-
return false;
|
|
1671
|
-
}
|
|
1672
|
-
if (/\bsections?\s+\d+(?:\.\d+)?\s*[-–—~]\s*\d+(?:\.\d+)?\b/.test(normalized)) {
|
|
1673
|
-
return true;
|
|
1674
|
-
}
|
|
1675
|
-
if (/(?:^|[#\s/])\d+(?:\.\d+)?\s*[-–—~至到]\s*\d+(?:\.\d+)?(?:\D|$)/.test(normalized)) {
|
|
1676
|
-
return true;
|
|
1677
|
-
}
|
|
1678
|
-
return /\b(chapter|section)\b.*\b(full|entire|whole|all)\b/.test(normalized);
|
|
1679
|
-
}
|
|
1680
|
-
function hasCoverageBodyDrift(projectRoot, card) {
|
|
1681
|
-
if (card.coverage.length === 0) {
|
|
1682
|
-
return false;
|
|
1683
|
-
}
|
|
1684
|
-
const content = safeRead(path.join(projectRoot, card.path));
|
|
1685
|
-
return content !== null && /\bCoverage:\s*Not specified\./i.test(extractMarkdownBody(content));
|
|
1686
|
-
}
|
|
1687
1616
|
function isDraftPlaceholderCoverage(value) {
|
|
1688
1617
|
const normalized = value.trim().toLowerCase();
|
|
1689
1618
|
return (normalized.startsWith("<") || normalized.endsWith(">"))
|
|
@@ -1700,13 +1629,9 @@ function isDraftPlaceholderVerification(value) {
|
|
|
1700
1629
|
}
|
|
1701
1630
|
function ingestLinkedAtomicMetadataErrors(relativePath, metadata) {
|
|
1702
1631
|
const errors = [];
|
|
1703
|
-
const
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
for (const [field, values] of requiredLists) {
|
|
1707
|
-
if (values.length === 0) {
|
|
1708
|
-
errors.push(`${relativePath} active ingest-linked atomic knowledge must include ${field}.`);
|
|
1709
|
-
}
|
|
1632
|
+
const primaryContent = primaryAtomicValue(metadata);
|
|
1633
|
+
if (primaryContent && hasPlaceholderText(primaryContent)) {
|
|
1634
|
+
errors.push(`${relativePath} active ingest-linked atomic knowledge must replace scaffold placeholder content.`);
|
|
1710
1635
|
}
|
|
1711
1636
|
return errors;
|
|
1712
1637
|
}
|
|
@@ -1820,16 +1745,30 @@ function isPrdSourcePath(sourcePath) {
|
|
|
1820
1745
|
}
|
|
1821
1746
|
return /(^|\/)docs\/[^/]*(?:prd|requirement|需求)[^/]*\.md$/i.test(normalized);
|
|
1822
1747
|
}
|
|
1823
|
-
function
|
|
1824
|
-
return card.type === "prd_semantics" || card.type === "module_summary"
|
|
1748
|
+
function isPrdKnowledgeQualityCard(card) {
|
|
1749
|
+
return card.type === "prd_semantics" || card.type === "module_summary";
|
|
1750
|
+
}
|
|
1751
|
+
function isThinSelfContainedKnowledge(card) {
|
|
1752
|
+
return normalize(primaryCardContent(card)).length < MIN_SELF_CONTAINED_PRD_CONTENT_CHARS;
|
|
1753
|
+
}
|
|
1754
|
+
function hasPlaceholderKnowledgeContent(card) {
|
|
1755
|
+
return hasPlaceholderText(primaryCardContent(card));
|
|
1756
|
+
}
|
|
1757
|
+
function primaryCardContent(card) {
|
|
1758
|
+
return card.rule ?? card.workflow ?? card.permission ?? card.term ?? card.note ?? card.summary;
|
|
1759
|
+
}
|
|
1760
|
+
function hasPlaceholderText(value) {
|
|
1761
|
+
const normalized = value.trim().toLowerCase();
|
|
1762
|
+
return /^<.*>$/.test(normalized) || normalized.includes(" distilled from ") || normalized.includes("scaffold placeholder");
|
|
1825
1763
|
}
|
|
1826
1764
|
function prdSemanticMetadataErrors(relativePath, metadata) {
|
|
1827
1765
|
const errors = [];
|
|
1828
|
-
|
|
1829
|
-
|
|
1766
|
+
const content = primaryAtomicValue(metadata) ?? metadata.summary ?? "";
|
|
1767
|
+
if (normalize(content).length < MIN_SELF_CONTAINED_PRD_CONTENT_CHARS) {
|
|
1768
|
+
errors.push(`${relativePath} active PRD semantics must be self-contained enough to replace deleted PRD source material.`);
|
|
1830
1769
|
}
|
|
1831
|
-
if (
|
|
1832
|
-
errors.push(`${relativePath} active PRD semantics must
|
|
1770
|
+
if (hasPlaceholderText(content)) {
|
|
1771
|
+
errors.push(`${relativePath} active PRD semantics must replace scaffold placeholder content.`);
|
|
1833
1772
|
}
|
|
1834
1773
|
return errors;
|
|
1835
1774
|
}
|