kibi-cli 2.25.0 → 2.26.2
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/cli-errors.d.ts +14 -0
- package/dist/cli-errors.d.ts.map +1 -1
- package/dist/cli-errors.js +31 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +28 -0
- package/dist/commands/migrate.d.ts +2 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +125 -6
- package/dist/commands/sync/cache.d.ts +30 -0
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +61 -0
- package/dist/commands/sync/extraction.d.ts +6 -0
- package/dist/commands/sync/extraction.d.ts.map +1 -1
- package/dist/commands/sync/extraction.js +4 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +27 -1
- package/dist/intent-search.d.ts.map +1 -1
- package/dist/intent-search.js +14 -3
- package/dist/operations/bootstrap/claim-restatement.d.ts +37 -0
- package/dist/operations/bootstrap/claim-restatement.d.ts.map +1 -0
- package/dist/operations/bootstrap/claim-restatement.js +83 -0
- package/dist/operations/bootstrap/generate.d.ts.map +1 -1
- package/dist/operations/bootstrap/generate.js +23 -12
- package/dist/operations/bootstrap/intent-claims.d.ts +5 -2
- package/dist/operations/bootstrap/intent-claims.d.ts.map +1 -1
- package/dist/operations/bootstrap/intent-claims.js +44 -4
- package/dist/operations/migration/requirement-status.d.ts +41 -2
- package/dist/operations/migration/requirement-status.d.ts.map +1 -1
- package/dist/operations/migration/requirement-status.js +75 -7
- package/dist/operations/migration/schema6.d.ts +9 -5
- package/dist/operations/migration/schema6.d.ts.map +1 -1
- package/dist/operations/migration/schema6.js +82 -13
- package/dist/operations/migration/symbol-coordinates-refresh.d.ts +36 -0
- package/dist/operations/migration/symbol-coordinates-refresh.d.ts.map +1 -0
- package/dist/operations/migration/symbol-coordinates-refresh.js +99 -0
- package/dist/operations/mutation/source-authoring.d.ts +9 -0
- package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
- package/dist/operations/mutation/source-authoring.js +50 -2
- package/dist/operations/mutation/stored-entity.d.ts +5 -0
- package/dist/operations/mutation/stored-entity.d.ts.map +1 -1
- package/dist/operations/mutation/types.d.ts +1 -1
- package/dist/operations/mutation/types.d.ts.map +1 -1
- package/dist/operations/mutation/uncompiled-source.d.ts +43 -0
- package/dist/operations/mutation/uncompiled-source.d.ts.map +1 -0
- package/dist/operations/mutation/uncompiled-source.js +115 -0
- package/dist/operations/mutation/upsert.d.ts +6 -0
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +32 -5
- package/dist/operations/planning/apply-plan.d.ts.map +1 -1
- package/dist/operations/planning/apply-plan.js +4 -2
- package/dist/public/impact/analyzer.d.ts.map +1 -1
- package/dist/public/impact/analyzer.js +13 -3
- package/dist/public/impact/diagnostics.js +1 -1
- package/dist/public/impact/unowned-files.d.ts +31 -0
- package/dist/public/impact/unowned-files.d.ts.map +1 -0
- package/dist/public/impact/unowned-files.js +171 -0
- package/dist/public/operations/catalog.d.ts +2 -2
- package/dist/public/operations/discovery-entities.d.ts +11 -0
- package/dist/public/operations/discovery-entities.d.ts.map +1 -1
- package/dist/public/operations/discovery-entities.js +28 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +13 -1
- package/dist/public/operations/migration-plan.d.ts +25 -0
- package/dist/public/operations/migration-plan.d.ts.map +1 -1
- package/dist/public/operations/migration-plan.js +40 -0
- package/dist/public/operations/result-envelope.d.ts +2 -0
- package/dist/public/operations/result-envelope.d.ts.map +1 -1
- package/dist/public/operations/result-envelope.js +1 -0
- package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
- package/dist/public/operations/source-relationship-parity.js +22 -7
- package/dist/public/operations/specs/check.d.ts +1 -1
- package/dist/public/operations/specs/check.js +1 -1
- package/dist/public/operations/specs/upsert.d.ts +1 -1
- package/dist/public/operations/specs/upsert.js +1 -1
- package/dist/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/dist/public/skills/kibi-usage/SKILL.md +11 -5
- package/dist/search-answer.d.ts +9 -0
- package/dist/search-answer.d.ts.map +1 -1
- package/dist/search-answer.js +66 -3
- package/dist/traceability/git-change-snapshot.d.ts.map +1 -1
- package/dist/traceability/git-change-snapshot.js +44 -3
- package/dist/traceability/staged-diagnostics.d.ts +1 -1
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-migration.d.ts +13 -0
- package/dist/traceability/staged-migration.d.ts.map +1 -0
- package/dist/traceability/staged-migration.js +59 -0
- package/dist/traceability/staged-symbols-manifest.d.ts +11 -0
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +50 -16
- package/package.json +2 -2
- package/src/public/impact/analyzer.ts +21 -7
- package/src/public/impact/diagnostics.ts +1 -1
- package/src/public/impact/unowned-files.ts +229 -0
- package/src/public/operations/discovery-entities.ts +36 -0
- package/src/public/operations/discovery-executors.ts +17 -1
- package/src/public/operations/migration-plan.ts +72 -0
- package/src/public/operations/result-envelope.ts +3 -0
- package/src/public/operations/source-relationship-parity.ts +22 -7
- package/src/public/operations/specs/check.ts +1 -1
- package/src/public/operations/specs/upsert.ts +1 -1
- package/src/public/skills/kibi-bootstrap/SKILL.md +2 -2
- package/src/public/skills/kibi-usage/SKILL.md +11 -5
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
18
|
import path from "node:path";
|
|
19
|
+
import { extractFromMarkdown } from "../../extractors/markdown.js";
|
|
20
|
+
import { withAddedTag } from "./entity-body-context.js";
|
|
19
21
|
import { planInventoryRederivation, } from "./inventory-rederive.js";
|
|
20
22
|
import { listLaneMarkdownFiles, readText, readTopLevelField, sliceFrontmatter, withTopLevelField, writeFileAtomically, } from "./kb-sources.js";
|
|
21
23
|
/**
|
|
@@ -25,8 +27,12 @@ import { listLaneMarkdownFiles, readText, readTopLevelField, sliceFrontmatter, w
|
|
|
25
27
|
* it is not a current requirement: the proof ladder skips it and the
|
|
26
28
|
* ingestion boundary does not validate its semantic inventory, so inventory
|
|
27
29
|
* drift stays hidden until someone maps the status. Which current status a
|
|
28
|
-
* legacy one means is a semantic choice, so the mapping is only
|
|
29
|
-
* the latent drift is computed as if the suggestion were applied.
|
|
30
|
+
* legacy one means is usually a semantic choice, so the mapping is only
|
|
31
|
+
* suggested; the latent drift is computed as if the suggestion were applied.
|
|
32
|
+
* Delivery vocabulary (`implemented`, `done`, `completed`) has one lossless
|
|
33
|
+
* reading, `closed` plus the tag `implemented`, and is mapped automatically:
|
|
34
|
+
* those statuses are outside the entity schema, so `kibi sync` cannot
|
|
35
|
+
* compile such a requirement at all until it is mapped.
|
|
30
36
|
*/
|
|
31
37
|
/** Statuses `kibi check` accepts on a requirement (canonical and legacy). */
|
|
32
38
|
const REQUIREMENT_STATUSES = new Set([
|
|
@@ -50,6 +56,29 @@ export const SUGGESTED_REQUIREMENT_STATUS = {
|
|
|
50
56
|
superseded: "closed",
|
|
51
57
|
rejected: "closed",
|
|
52
58
|
};
|
|
59
|
+
/** The tag that keeps a delivered legacy requirement's meaning once closed. */
|
|
60
|
+
// implements REQ-cli-schema-migration
|
|
61
|
+
export const DELIVERED_REQUIREMENT_TAG = "implemented";
|
|
62
|
+
/**
|
|
63
|
+
* Legacy delivery statuses and their one lossless current reading: `closed`
|
|
64
|
+
* with the tag `implemented`, which kb.pl reads as a delivered requirement
|
|
65
|
+
* that still owes coverage. `kibi migrate` applies these without review.
|
|
66
|
+
*/
|
|
67
|
+
// implements REQ-cli-schema-migration
|
|
68
|
+
export const DELIVERED_REQUIREMENT_STATUS = {
|
|
69
|
+
implemented: { status: "closed", tag: DELIVERED_REQUIREMENT_TAG },
|
|
70
|
+
done: { status: "closed", tag: DELIVERED_REQUIREMENT_TAG },
|
|
71
|
+
completed: { status: "closed", tag: DELIVERED_REQUIREMENT_TAG },
|
|
72
|
+
};
|
|
73
|
+
function extractionErrorOf(absolutePath) {
|
|
74
|
+
try {
|
|
75
|
+
extractFromMarkdown(absolutePath);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return error instanceof Error ? error.message : String(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
53
82
|
function statusOf(content) {
|
|
54
83
|
const slice = sliceFrontmatter(content);
|
|
55
84
|
return slice === null ? undefined : readTopLevelField(slice.text, "status");
|
|
@@ -73,12 +102,15 @@ export function planRequirementStatusMappings(workspaceRoot) {
|
|
|
73
102
|
const requirementId = typeof id === "string" && id.length > 0
|
|
74
103
|
? id
|
|
75
104
|
: path.basename(file.relativePath, ".md");
|
|
76
|
-
const
|
|
105
|
+
const automatic = DELIVERED_REQUIREMENT_STATUS[status] ?? null;
|
|
106
|
+
const suggestedStatus = automatic?.status ?? SUGGESTED_REQUIREMENT_STATUS[status] ?? null;
|
|
77
107
|
findings.push({
|
|
78
108
|
requirementId,
|
|
79
109
|
path: file.relativePath,
|
|
80
110
|
status,
|
|
81
111
|
suggestedStatus,
|
|
112
|
+
automatic,
|
|
113
|
+
extractionError: extractionErrorOf(file.absolutePath),
|
|
82
114
|
latentDrift: suggestedStatus === null
|
|
83
115
|
? null
|
|
84
116
|
: (planInventoryRederivation(workspaceRoot, requirementId, {
|
|
@@ -88,18 +120,54 @@ export function planRequirementStatusMappings(workspaceRoot) {
|
|
|
88
120
|
}
|
|
89
121
|
return findings.sort((left, right) => left.requirementId.localeCompare(right.requirementId));
|
|
90
122
|
}
|
|
123
|
+
function hasTag(content, tag) {
|
|
124
|
+
const slice = sliceFrontmatter(content);
|
|
125
|
+
const tags = slice === null ? undefined : readTopLevelField(slice.text, "tags");
|
|
126
|
+
return Array.isArray(tags) && tags.includes(tag);
|
|
127
|
+
}
|
|
91
128
|
/**
|
|
92
129
|
* Set the status of one requirement from `from` to `to`, replacing only its
|
|
93
|
-
* `status` line
|
|
130
|
+
* `status` line, and add `tag` when given and missing. Refuses when the
|
|
131
|
+
* authored status is no longer `from`.
|
|
94
132
|
*/
|
|
95
133
|
// implements REQ-cli-schema-migration
|
|
96
|
-
export function applyRequirementStatusMapping(workspaceRoot, relativePath, from, to) {
|
|
134
|
+
export function applyRequirementStatusMapping(workspaceRoot, relativePath, from, to, tag) {
|
|
97
135
|
const absolute = path.join(workspaceRoot, relativePath);
|
|
98
136
|
const content = readText(absolute);
|
|
99
137
|
if (content === null || statusOf(content) !== from)
|
|
100
138
|
throw new Error(`${relativePath} no longer has status ${from}; rerun kibi migrate and approve the new plan.`);
|
|
101
|
-
const
|
|
139
|
+
const mapped = withTopLevelField(content, "status", to);
|
|
140
|
+
const next = mapped === null || tag === undefined || hasTag(mapped, tag)
|
|
141
|
+
? mapped
|
|
142
|
+
: withAddedTag(mapped, tag);
|
|
102
143
|
if (next === null)
|
|
103
|
-
throw new Error(`${relativePath}: status could not be set to ${to} without changing other frontmatter fields; set it by hand.`);
|
|
144
|
+
throw new Error(`${relativePath}: status could not be set to ${to}${tag === undefined ? "" : ` with tag ${tag}`} without changing other frontmatter fields; set it by hand.`);
|
|
104
145
|
writeFileAtomically(absolute, next);
|
|
105
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Map every requirement whose status is delivery vocabulary to `closed` with
|
|
149
|
+
* the tag `implemented`, front matter only. With `only`, maps just those
|
|
150
|
+
* requirement ids (a plan action applies what it planned). Idempotent.
|
|
151
|
+
*/
|
|
152
|
+
// implements REQ-cli-schema-migration
|
|
153
|
+
export function applyDeliveredStatusMappings(workspaceRoot, only) {
|
|
154
|
+
const mapped = [];
|
|
155
|
+
const skipped = [];
|
|
156
|
+
for (const finding of planRequirementStatusMappings(workspaceRoot)) {
|
|
157
|
+
if (finding.automatic === null)
|
|
158
|
+
continue;
|
|
159
|
+
if (only !== undefined && !only.has(finding.requirementId))
|
|
160
|
+
continue;
|
|
161
|
+
try {
|
|
162
|
+
applyRequirementStatusMapping(workspaceRoot, finding.path, finding.status, finding.automatic.status, finding.automatic.tag);
|
|
163
|
+
mapped.push(finding);
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
skipped.push({
|
|
167
|
+
path: finding.path,
|
|
168
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return { mapped, skipped };
|
|
173
|
+
}
|
|
@@ -17,16 +17,20 @@ export declare const SEMANTIC_INVENTORY_REVIEW_CODE = "semantic_inventory_review
|
|
|
17
17
|
export declare const SEMANTIC_INVENTORY_REDERIVE_DROP_GROUNDING_CODE = "semantic_inventory_rederive_drop_grounding";
|
|
18
18
|
/** Review, applied only when approved by id: map a legacy requirement status. */
|
|
19
19
|
export declare const REQ_STATUS_VOCABULARY_MAP_CODE = "req_status_vocabulary_map";
|
|
20
|
+
/** Automatic: map delivery statuses to `closed` plus the tag `implemented`. */
|
|
21
|
+
export declare const REQ_STATUS_DELIVERED_MAP_CODE = "req_status_delivered_map";
|
|
22
|
+
export declare const REQ_STATUS_DELIVERED_MAP_ACTION_ID = "req-status-delivered-map";
|
|
20
23
|
/** Automatic: stamp `origin: {kind: migration}` on authored entities. */
|
|
21
24
|
export declare const ENTITY_ORIGIN_BACKFILL_CODE = "entity_origin_backfill";
|
|
22
25
|
export declare const ENTITY_ORIGIN_BACKFILL_ACTION_ID = "entity-origin-backfill";
|
|
23
26
|
export { CLOSE_SUPERSEDED_REQUIREMENTS_CODE, MIGRATION_SYNC_ACTION_ID, MIGRATION_SYNC_CODE, SOURCE_PATH_REWRITE_CODE, };
|
|
24
27
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
28
|
+
* Requirements whose status is outside the vocabulary. Delivery vocabulary
|
|
29
|
+
* gets one automatic action (see deliveredStatusAction). Every other status
|
|
30
|
+
* gets one review action (the target status is a semantic choice) carrying
|
|
31
|
+
* the suggested mapping and the inventory drift the requirement shows once
|
|
32
|
+
* mapped. With a suggestion it is executable when approved by id; the
|
|
33
|
+
* re-derivation of the latent drift depends on it.
|
|
30
34
|
*/
|
|
31
35
|
export declare function requirementStatusActions(workspaceRoot: string): MigrationAction[];
|
|
32
36
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema6.d.ts","sourceRoot":"","sources":["../../../src/operations/migration/schema6.ts"],"names":[],"mappings":"AAwBA,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAEL,kCAAkC,EAClC,wBAAwB,EACxB,mBAAmB,EAEnB,wBAAwB,EAOzB,MAAM,kDAAkD,CAAC;
|
|
1
|
+
{"version":3,"file":"schema6.d.ts","sourceRoot":"","sources":["../../../src/operations/migration/schema6.ts"],"names":[],"mappings":"AAwBA,OAAO,EACL,KAAK,eAAe,EAErB,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAEL,kCAAkC,EAClC,wBAAwB,EACxB,mBAAmB,EAEnB,wBAAwB,EAOzB,MAAM,kDAAkD,CAAC;AAkE1D,gEAAgE;AAEhE,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,+EAA+E;AAE/E,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C,4DAA4D;AAE5D,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAElD,yEAAyE;AAEzE,eAAO,MAAM,gCAAgC,gCAAgC,CAAC;AAC9E,gEAAgE;AAEhE,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAC1E;;;GAGG;AAEH,eAAO,MAAM,+CAA+C,+CACd,CAAC;AAC/C,iFAAiF;AAEjF,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAC1E,+EAA+E;AAE/E,eAAO,MAAM,6BAA6B,6BAA6B,CAAC;AAExE,eAAO,MAAM,kCAAkC,6BAA6B,CAAC;AAC7E,yEAAyE;AAEzE,eAAO,MAAM,2BAA2B,2BAA2B,CAAC;AAEpE,eAAO,MAAM,gCAAgC,2BAA2B,CAAC;AACzE,OAAO,EACL,kCAAkC,EAClC,wBAAwB,EACxB,mBAAmB,EACnB,wBAAwB,GACzB,CAAC;AAiNF;;;;;;;GAOG;AAEH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,MAAM,GACpB,eAAe,EAAE,CAkFnB;AAyBD;;;GAGG;AAEH,wBAAgB,+BAA+B,CAC7C,aAAa,EAAE,MAAM,GACpB,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAsDnE;AAGD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IACpC,uDAAuD;IACvD,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CAChC,CAAC,CAAC;AAEH;;;;;;GAMG;AAEH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE;IACnD,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,GAAG,wBAAwB,CA+P3B;AAED;;;;;;GAMG;AAEH,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG;IAClE,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,sBAAsB,EAAE,MAAM,EAAE,CAAC;CAClC,CAuCA;AAED;;;GAGG;AAEH,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,MAAM,GACpB,eAAe,GAAG,IAAI,CAuBxB;AAED,qEAAqE;AAErE,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,eAAe,CAEjB;AAgDD,KAAK,YAAY,GAAG,QAAQ,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC,CAAC;AAEH,4DAA4D;AAE5D,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CA0If;AAED;;;GAGG;AAEH,eAAO,MAAM,+BAA+B,EAAE,WAAW,CAAC,MAAM,CAK9D,CAAC;AAGH,eAAO,MAAM,uBAAuB,EAAE,WAAW,CAAC,MAAM,CAatD,CAAC"}
|
|
@@ -21,16 +21,18 @@ import { FIRMNESS_FROM_ORIGIN_SCHEMA_VERSION } from "../../public/operations/dec
|
|
|
21
21
|
import { migrationAction, } from "../../public/operations/migration-plan.js";
|
|
22
22
|
import { CLOSE_SUPERSEDED_REQUIREMENTS_ACTION_ID, CLOSE_SUPERSEDED_REQUIREMENTS_CODE, MIGRATION_SYNC_ACTION_ID, MIGRATION_SYNC_CODE, SOURCE_PATH_REWRITE_ACTION_ID, SOURCE_PATH_REWRITE_CODE, closeSupersededRequirementsActionInput, exceptionUnapprovedActionInput, migrationSyncActionInput, sourcePathReviewActionInput, sourcePathRewriteActionInput, supersessionCycleReviewActionInput, } from "../../public/operations/schema6-check-actions.js";
|
|
23
23
|
import { readAllShards } from "../../relationships/shards.js";
|
|
24
|
+
import { KB_PATHS } from "../../utils/kb-paths.js";
|
|
24
25
|
import { CONTEXT_MISSING_TAG_CODE, SEMANTIC_TEXT_PIN_CODE, applyContextMissingTags, applySemanticTextPins, countByType, planContextMissingTags, planSemanticTextPins, } from "./entity-body-context.js";
|
|
25
26
|
import { FIRM_SUPERSEDE_ACKNOWLEDGE_CODE, applyFirmSupersedeAcknowledgement, planFirmSupersedeAcknowledgement, } from "./firm-supersede.js";
|
|
26
27
|
import { applyInventoryRederivation, applyInventoryRederivationDroppingGrounding, planInventoryRederivation, } from "./inventory-rederive.js";
|
|
27
28
|
import { listLaneMarkdownFiles } from "./kb-sources.js";
|
|
28
29
|
import { applyOriginBackfill, planOriginBackfill } from "./origin-backfill.js";
|
|
29
30
|
import { POLARITY_VALUE_BACKFILL_CODE, applyPolarityValueBackfill, planPolarityValueBackfill, } from "./polarity-values.js";
|
|
30
|
-
import { applyRequirementStatusMapping, planRequirementStatusMappings, } from "./requirement-status.js";
|
|
31
|
+
import { DELIVERED_REQUIREMENT_TAG, applyDeliveredStatusMappings, applyRequirementStatusMapping, planRequirementStatusMappings, } from "./requirement-status.js";
|
|
31
32
|
import { applySourcePathRepairs, findSourceRepairs, plannedSourcePathRepairs, } from "./source-paths.js";
|
|
32
33
|
import { applySupersededClosures, planSupersededClosures, } from "./superseded-closure.js";
|
|
33
34
|
import { SUPERSEDES_REASON_ACKNOWLEDGE_CODE, applySupersedesReasonAcknowledgement, planSupersedesReasonAcknowledgement, } from "./supersedes-reason.js";
|
|
35
|
+
import { coordinateRefreshFailureMessage, readGeneratedManifestBytes, refreshCoordinatesAfterMigration, } from "./symbol-coordinates-refresh.js";
|
|
34
36
|
import { SYMBOL_LEGACY_LINK_MARK_ACTION_ID, SYMBOL_LEGACY_LINK_MARK_CODE, applySymbolLegacyLinks, planSymbolLegacyLinks, } from "./symbol-legacy-links.js";
|
|
35
37
|
/** The KB schema generation that introduced entity `origin`. */
|
|
36
38
|
// implements REQ-kibi-schema6-migration, REQ-cli-schema-migration
|
|
@@ -56,6 +58,11 @@ export const SEMANTIC_INVENTORY_REDERIVE_DROP_GROUNDING_CODE = "semantic_invento
|
|
|
56
58
|
/** Review, applied only when approved by id: map a legacy requirement status. */
|
|
57
59
|
// implements REQ-cli-schema-migration
|
|
58
60
|
export const REQ_STATUS_VOCABULARY_MAP_CODE = "req_status_vocabulary_map";
|
|
61
|
+
/** Automatic: map delivery statuses to `closed` plus the tag `implemented`. */
|
|
62
|
+
// implements REQ-cli-schema-migration
|
|
63
|
+
export const REQ_STATUS_DELIVERED_MAP_CODE = "req_status_delivered_map";
|
|
64
|
+
// implements REQ-cli-schema-migration
|
|
65
|
+
export const REQ_STATUS_DELIVERED_MAP_ACTION_ID = "req-status-delivered-map";
|
|
59
66
|
/** Automatic: stamp `origin: {kind: migration}` on authored entities. */
|
|
60
67
|
// implements REQ-kibi-schema6-migration, REQ-cli-schema-migration
|
|
61
68
|
export const ENTITY_ORIGIN_BACKFILL_CODE = "entity_origin_backfill";
|
|
@@ -210,16 +217,52 @@ function plannedDrops(action) {
|
|
|
210
217
|
: []);
|
|
211
218
|
}
|
|
212
219
|
/**
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
220
|
+
* The automatic action for requirements whose status is delivery vocabulary
|
|
221
|
+
* (`implemented`, `done`, `completed`): sync cannot compile them, and their
|
|
222
|
+
* one lossless reading is `closed` plus the tag `implemented`.
|
|
223
|
+
*/
|
|
224
|
+
function deliveredStatusAction(findings) {
|
|
225
|
+
return migrationAction({
|
|
226
|
+
id: REQ_STATUS_DELIVERED_MAP_ACTION_ID,
|
|
227
|
+
code: REQ_STATUS_DELIVERED_MAP_CODE,
|
|
228
|
+
category: "schema",
|
|
229
|
+
safety: "automatic",
|
|
230
|
+
autoApplicable: true,
|
|
231
|
+
invocation: { kind: "cli", command_argv: ["kibi", "migrate", "--yes"] },
|
|
232
|
+
affectedEntityIds: findings.map((finding) => finding.requirementId),
|
|
233
|
+
affectedFiles: findings.map((finding) => finding.path),
|
|
234
|
+
postconditions: [{ rule: "req-status-vocabulary", findings: 0 }],
|
|
235
|
+
evidence: {
|
|
236
|
+
reason: "Delivery statuses are outside the entity schema, so kibi sync cannot compile these requirements and every store-backed tool misses them. Each becomes status closed with the tag implemented, which Kibi reads as a delivered requirement that still owes coverage; only the status line and the tags change.",
|
|
237
|
+
count: findings.length,
|
|
238
|
+
mappings: findings.map((finding) => ({
|
|
239
|
+
requirementId: finding.requirementId,
|
|
240
|
+
path: finding.path,
|
|
241
|
+
from: finding.status,
|
|
242
|
+
to: finding.automatic?.status ?? "closed",
|
|
243
|
+
tag: finding.automatic?.tag ?? DELIVERED_REQUIREMENT_TAG,
|
|
244
|
+
})),
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Requirements whose status is outside the vocabulary. Delivery vocabulary
|
|
250
|
+
* gets one automatic action (see deliveredStatusAction). Every other status
|
|
251
|
+
* gets one review action (the target status is a semantic choice) carrying
|
|
252
|
+
* the suggested mapping and the inventory drift the requirement shows once
|
|
253
|
+
* mapped. With a suggestion it is executable when approved by id; the
|
|
254
|
+
* re-derivation of the latent drift depends on it.
|
|
218
255
|
*/
|
|
219
256
|
// implements REQ-cli-schema-migration, REQ-kibi-proposition-complete-ingestion
|
|
220
257
|
export function requirementStatusActions(workspaceRoot) {
|
|
221
258
|
const actions = [];
|
|
222
|
-
|
|
259
|
+
const findings = planRequirementStatusMappings(workspaceRoot);
|
|
260
|
+
const delivered = findings.filter((finding) => finding.automatic !== null);
|
|
261
|
+
if (delivered.length > 0)
|
|
262
|
+
actions.push(deliveredStatusAction(delivered));
|
|
263
|
+
for (const finding of findings) {
|
|
264
|
+
if (finding.automatic !== null)
|
|
265
|
+
continue;
|
|
223
266
|
const id = `req-status-vocabulary-map-${finding.requirementId}`;
|
|
224
267
|
const drift = finding.latentDrift;
|
|
225
268
|
const target = finding.suggestedStatus;
|
|
@@ -232,7 +275,9 @@ export function requirementStatusActions(workspaceRoot) {
|
|
|
232
275
|
invocation: target === null
|
|
233
276
|
? {
|
|
234
277
|
kind: "review",
|
|
235
|
-
instruction:
|
|
278
|
+
instruction: finding.extractionError === null
|
|
279
|
+
? `${finding.requirementId} (${finding.path}) has status ${finding.status}, which is not a requirement status, so it is skipped by the proof ladder and its semantic inventory is never validated. Set status to open, in_progress or closed (legacy: active, approved) in the file, or use proof_exempt to park it, then rerun kibi migrate.`
|
|
280
|
+
: `${finding.requirementId} (${finding.path}) has status ${finding.status}, which is not a status Kibi knows, so kibi sync cannot compile it (${finding.extractionError}) and kb_query, kb_coverage and kb_upsert do not see it. Set status to open, in_progress or closed in the file (closed with the tag implemented for delivered work), then run kibi sync and rerun kibi migrate.`,
|
|
236
281
|
}
|
|
237
282
|
: { kind: "cli", command_argv: approvalArgv([id]) },
|
|
238
283
|
affectedEntityIds: [finding.requirementId],
|
|
@@ -241,9 +286,14 @@ export function requirementStatusActions(workspaceRoot) {
|
|
|
241
286
|
dispositionRequired: true,
|
|
242
287
|
allowedDispositions: ["fixed", "deferred"],
|
|
243
288
|
evidence: {
|
|
244
|
-
reason:
|
|
289
|
+
reason: finding.extractionError === null
|
|
290
|
+
? `Status ${finding.status} is ADR or lifecycle vocabulary, not a requirement status: the requirement is skipped by the proof ladder and kibi sync never validates its semantic inventory.`
|
|
291
|
+
: `Status ${finding.status} is outside the entity schema, so kibi sync cannot compile the requirement at all.`,
|
|
245
292
|
status: finding.status,
|
|
246
293
|
suggestedStatus: target,
|
|
294
|
+
...(finding.extractionError === null
|
|
295
|
+
? {}
|
|
296
|
+
: { extractionError: finding.extractionError }),
|
|
247
297
|
...(target === null
|
|
248
298
|
? {}
|
|
249
299
|
: {
|
|
@@ -515,9 +565,13 @@ export function buildSchema6MigrationFragment(input) {
|
|
|
515
565
|
if (!plan.safe && plan.rederivableByDroppingGrounding)
|
|
516
566
|
actions.push(dropGroundingAction(plan));
|
|
517
567
|
}
|
|
518
|
-
// Requirements with a status outside the vocabulary:
|
|
519
|
-
//
|
|
520
|
-
|
|
568
|
+
// Requirements with a status outside the vocabulary: delivery statuses are
|
|
569
|
+
// mapped automatically; any other mapping is a review, and its latent drift
|
|
570
|
+
// is re-derived only after an approved mapping.
|
|
571
|
+
const statusActions = requirementStatusActions(input.workspaceRoot);
|
|
572
|
+
actions.push(...statusActions);
|
|
573
|
+
if (statusActions.some((action) => action.id === REQ_STATUS_DELIVERED_MAP_ACTION_ID))
|
|
574
|
+
sourceRewriteActionIds.push(REQ_STATUS_DELIVERED_MAP_ACTION_ID);
|
|
521
575
|
if (drift.some((plan) => !plan.safe)) {
|
|
522
576
|
diagnostics.push(`${drift.filter((plan) => !plan.safe).length} requirement inventory(ies) need a manual fix before kibi sync succeeds; see the semantic_inventory_review actions.`);
|
|
523
577
|
}
|
|
@@ -637,7 +691,9 @@ export function symbolLegacyLinkAction(workspaceRoot) {
|
|
|
637
691
|
autoApplicable: true,
|
|
638
692
|
invocation: { kind: "cli", command_argv: ["kibi", "migrate", "--yes"] },
|
|
639
693
|
affectedEntityIds: plan.targets.map((target) => target.id),
|
|
640
|
-
|
|
694
|
+
// A mark changes the record's coordinate identity, so the action also
|
|
695
|
+
// regenerates the coordinate artifact.
|
|
696
|
+
affectedFiles: [plan.manifestPath, KB_PATHS.symbolCoordinates],
|
|
641
697
|
postconditions: [{ rule: "symbol_granularity_violation", findings: 0 }],
|
|
642
698
|
evidence: {
|
|
643
699
|
reason: "These symbols link a source file coarsely while it declares narrower behavioral symbols, so the staged check (kibi check --staged) blocks the next commit that touches them. Each gets granularity_reason: legacy-link (accepted legacy granularity); no link moves and no other field changes.",
|
|
@@ -725,6 +781,12 @@ export async function applySchema6MigrationAction(action, context) {
|
|
|
725
781
|
applyRequirementStatusMapping(context.workspaceRoot, mapping.path, mapping.from, mapping.to);
|
|
726
782
|
return;
|
|
727
783
|
}
|
|
784
|
+
case REQ_STATUS_DELIVERED_MAP_CODE: {
|
|
785
|
+
const result = applyDeliveredStatusMappings(context.workspaceRoot, new Set(action.affectedEntityIds));
|
|
786
|
+
if (result.skipped.length > 0)
|
|
787
|
+
throw new Error(`Could not map the status of ${result.skipped.map((skip) => `${skip.path} (${skip.reason})`).join(", ")}; set status: closed and add the tag implemented by hand.`);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
728
790
|
case SEMANTIC_INVENTORY_REDERIVE_DROP_GROUNDING_CODE: {
|
|
729
791
|
const requirementId = action.affectedEntityIds[0];
|
|
730
792
|
const contractHash = action.evidence.contractHash;
|
|
@@ -737,6 +799,7 @@ export async function applySchema6MigrationAction(action, context) {
|
|
|
737
799
|
applyOriginBackfill(context.workspaceRoot, context.clock().toISOString());
|
|
738
800
|
return;
|
|
739
801
|
case SYMBOL_LEGACY_LINK_MARK_CODE: {
|
|
802
|
+
const before = readGeneratedManifestBytes(context.workspaceRoot);
|
|
740
803
|
const result = applySymbolLegacyLinks(context.workspaceRoot, {
|
|
741
804
|
...(typeof action.evidence.manifestHash === "string"
|
|
742
805
|
? { expectedHash: action.evidence.manifestHash }
|
|
@@ -745,6 +808,11 @@ export async function applySchema6MigrationAction(action, context) {
|
|
|
745
808
|
});
|
|
746
809
|
if (result.unmarked.length > 0)
|
|
747
810
|
throw new Error(`Could not mark ${result.unmarked.join(", ")} as legacy-link in place; add granularity_reason: legacy-link to them by hand.`);
|
|
811
|
+
// A legacy-link mark changes the record's coordinate identity; keep
|
|
812
|
+
// the generated artifact current so the next commit carries no drift.
|
|
813
|
+
const refresh = await refreshCoordinatesAfterMigration(context.workspaceRoot, before);
|
|
814
|
+
if (refresh.outcome === "failed")
|
|
815
|
+
console.warn(`Warning: ${coordinateRefreshFailureMessage(refresh.reason)}`);
|
|
748
816
|
return;
|
|
749
817
|
}
|
|
750
818
|
case CLOSE_SUPERSEDED_REQUIREMENTS_CODE: {
|
|
@@ -791,6 +859,7 @@ export const SCHEMA6_AUTOMATIC_CODES = new Set([
|
|
|
791
859
|
SUPERSEDES_REASON_ACKNOWLEDGE_CODE,
|
|
792
860
|
FIRM_SUPERSEDE_ACKNOWLEDGE_CODE,
|
|
793
861
|
SEMANTIC_INVENTORY_REDERIVE_CODE,
|
|
862
|
+
REQ_STATUS_DELIVERED_MAP_CODE,
|
|
794
863
|
ENTITY_ORIGIN_BACKFILL_CODE,
|
|
795
864
|
CLOSE_SUPERSEDED_REQUIREMENTS_CODE,
|
|
796
865
|
SOURCE_PATH_REWRITE_CODE,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bytes of the symbols manifest and its generated coordinate artifact at
|
|
3
|
+
* their canonical paths; null for a file that does not exist.
|
|
4
|
+
*/
|
|
5
|
+
export type GeneratedManifestBytes = Readonly<{
|
|
6
|
+
symbols: string | null;
|
|
7
|
+
coordinates: string | null;
|
|
8
|
+
}>;
|
|
9
|
+
/** Snapshot taken before a migration writes, to tell what it rewrote. */
|
|
10
|
+
export declare function readGeneratedManifestBytes(workspaceRoot: string): GeneratedManifestBytes;
|
|
11
|
+
/** What the post-migration coordinate refresh did. */
|
|
12
|
+
export type MigrationCoordinateRefresh = {
|
|
13
|
+
outcome: "skipped";
|
|
14
|
+
} | {
|
|
15
|
+
outcome: "refreshed";
|
|
16
|
+
} | {
|
|
17
|
+
outcome: "failed";
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Refresh generated symbol coordinates the way
|
|
22
|
+
* `kibi sync --refresh-symbol-coordinates` does, after a migration rewrote or
|
|
23
|
+
* moved the symbols manifest. The coordinate artifact binds each record by an
|
|
24
|
+
* identity hash that includes `granularity_reason`, so a legacy-link mark
|
|
25
|
+
* leaves the committed coordinates stale; without this refresh the pre-commit
|
|
26
|
+
* generated-manifest gate rejects the first commit after a migration.
|
|
27
|
+
*
|
|
28
|
+
* Skipped when `before` shows the run left both files untouched, or when the
|
|
29
|
+
* manifest has no symbols. A refresh failure is returned, not thrown: the
|
|
30
|
+
* migration itself is complete, and the caller tells the user to refresh by
|
|
31
|
+
* hand before committing.
|
|
32
|
+
*/
|
|
33
|
+
export declare function refreshCoordinatesAfterMigration(workspaceRoot: string, before?: GeneratedManifestBytes): Promise<MigrationCoordinateRefresh>;
|
|
34
|
+
/** The line a migration prints when its coordinate refresh failed. */
|
|
35
|
+
export declare function coordinateRefreshFailureMessage(reason: string): string;
|
|
36
|
+
//# sourceMappingURL=symbol-coordinates-refresh.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"symbol-coordinates-refresh.d.ts","sourceRoot":"","sources":["../../../src/operations/migration/symbol-coordinates-refresh.ts"],"names":[],"mappings":"AA0BA;;;GAGG;AAEH,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC,CAAC;AAMH,yEAAyE;AAEzE,wBAAgB,0BAA0B,CACxC,aAAa,EAAE,MAAM,GACpB,sBAAsB,CAMxB;AAYD,sDAAsD;AAEtD,MAAM,MAAM,0BAA0B,GAClC;IAAE,OAAO,EAAE,SAAS,CAAA;CAAE,GACtB;IAAE,OAAO,EAAE,WAAW,CAAA;CAAE,GACxB;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1C;;;;;;;;;;;;GAYG;AAEH,wBAAsB,gCAAgC,CACpD,aAAa,EAAE,MAAM,EACrB,MAAM,CAAC,EAAE,sBAAsB,GAC9B,OAAO,CAAC,0BAA0B,CAAC,CA0CrC;AAED,sEAAsE;AAEtE,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtE"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { load as parseYAML } from "js-yaml";
|
|
22
|
+
import { resolveSymbolsManifestPaths } from "../../utils/manifest-paths.js";
|
|
23
|
+
import { republishPendingSourceReceipt } from "../mutation/source-authoring.js";
|
|
24
|
+
import { withSymbolCompilerLock } from "../mutation/symbol-compiler-lock.js";
|
|
25
|
+
function readOrNull(filePath) {
|
|
26
|
+
return existsSync(filePath) ? readFileSync(filePath, "utf8") : null;
|
|
27
|
+
}
|
|
28
|
+
/** Snapshot taken before a migration writes, to tell what it rewrote. */
|
|
29
|
+
// implements REQ-cli-migration-first-commit
|
|
30
|
+
export function readGeneratedManifestBytes(workspaceRoot) {
|
|
31
|
+
const paths = resolveSymbolsManifestPaths(workspaceRoot);
|
|
32
|
+
return {
|
|
33
|
+
symbols: readOrNull(paths.symbolsPath),
|
|
34
|
+
coordinates: readOrNull(paths.coordinatesPath),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function hasSymbols(content) {
|
|
38
|
+
try {
|
|
39
|
+
const parsed = parseYAML(content);
|
|
40
|
+
return Array.isArray(parsed?.symbols) && parsed.symbols.length > 0;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// An unreadable manifest is reported by the refresh itself.
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Refresh generated symbol coordinates the way
|
|
49
|
+
* `kibi sync --refresh-symbol-coordinates` does, after a migration rewrote or
|
|
50
|
+
* moved the symbols manifest. The coordinate artifact binds each record by an
|
|
51
|
+
* identity hash that includes `granularity_reason`, so a legacy-link mark
|
|
52
|
+
* leaves the committed coordinates stale; without this refresh the pre-commit
|
|
53
|
+
* generated-manifest gate rejects the first commit after a migration.
|
|
54
|
+
*
|
|
55
|
+
* Skipped when `before` shows the run left both files untouched, or when the
|
|
56
|
+
* manifest has no symbols. A refresh failure is returned, not thrown: the
|
|
57
|
+
* migration itself is complete, and the caller tells the user to refresh by
|
|
58
|
+
* hand before committing.
|
|
59
|
+
*/
|
|
60
|
+
// implements REQ-cli-migration-first-commit
|
|
61
|
+
export async function refreshCoordinatesAfterMigration(workspaceRoot, before) {
|
|
62
|
+
const after = readGeneratedManifestBytes(workspaceRoot);
|
|
63
|
+
if (after.symbols === null || !hasSymbols(after.symbols))
|
|
64
|
+
return { outcome: "skipped" };
|
|
65
|
+
if (before !== undefined &&
|
|
66
|
+
before.symbols === after.symbols &&
|
|
67
|
+
before.coordinates === after.coordinates)
|
|
68
|
+
return { outcome: "skipped" };
|
|
69
|
+
const { symbolsPath, coordinatesPath } = resolveSymbolsManifestPaths(workspaceRoot);
|
|
70
|
+
try {
|
|
71
|
+
const { refreshManifestCoordinates } = await import("../../commands/sync/manifest.js");
|
|
72
|
+
await withSymbolCompilerLock(workspaceRoot, () => refreshManifestCoordinates(symbolsPath, workspaceRoot, {
|
|
73
|
+
refreshSymbolCoordinates: true,
|
|
74
|
+
}));
|
|
75
|
+
// Files the migration moved are still untracked and pinned by a
|
|
76
|
+
// pending-source receipt; rebind each to its refreshed bytes so the next
|
|
77
|
+
// sync does not refuse it as hash drift. A file without a receipt is
|
|
78
|
+
// left alone.
|
|
79
|
+
for (const filePath of [symbolsPath, coordinatesPath]) {
|
|
80
|
+
if (!existsSync(filePath))
|
|
81
|
+
continue;
|
|
82
|
+
republishPendingSourceReceipt(workspaceRoot, path.relative(workspaceRoot, filePath).split(path.sep).join("/"), createHash("sha256")
|
|
83
|
+
.update(readFileSync(filePath, "utf8"), "utf8")
|
|
84
|
+
.digest("hex"));
|
|
85
|
+
}
|
|
86
|
+
return { outcome: "refreshed" };
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
outcome: "failed",
|
|
91
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** The line a migration prints when its coordinate refresh failed. */
|
|
96
|
+
// implements REQ-cli-migration-first-commit
|
|
97
|
+
export function coordinateRefreshFailureMessage(reason) {
|
|
98
|
+
return `Could not refresh symbol coordinates after the migration (${reason}). Run 'kibi sync --refresh-symbol-coordinates' and stage .kb/symbols.yaml and .kb/symbol-coordinates.yaml before the first commit, or the pre-commit hook reports generated manifest drift.`;
|
|
99
|
+
}
|
|
@@ -43,6 +43,15 @@ export declare function normalizeAuthoredSourcePath(workspaceRoot: string, sourc
|
|
|
43
43
|
*/
|
|
44
44
|
export declare function configuredSourceTarget(workspaceRoot: string, type: string): string | undefined;
|
|
45
45
|
export declare function hasConfiguredSourceTarget(workspaceRoot: string, type: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The absolute authored document an upsert of an entity the store does not
|
|
48
|
+
* hold would write: the same resolution writeSourceForUpsert uses, so a check
|
|
49
|
+
* on that file judges the file the writer merges into. Undefined when the
|
|
50
|
+
* writer has no target (it reports DOCUMENT_PATH_REQUIRED itself).
|
|
51
|
+
*/
|
|
52
|
+
export declare function newEntitySourceTarget(context: OperationContext, input: UpsertInput): string | undefined;
|
|
53
|
+
/** The front matter of an authored document; throws on YAML it cannot parse. */
|
|
54
|
+
export declare function authoredFrontmatter(content: string): Record<string, unknown>;
|
|
46
55
|
/** Generated coordinate fields belong exclusively to `.kb/symbol-coordinates.yaml`. */
|
|
47
56
|
export declare const GENERATED_SYMBOL_FIELDS: readonly ["sourceLine", "sourceColumn", "sourceEndLine", "sourceEndColumn", "coordinatesGeneratedAt"];
|
|
48
57
|
export declare function manifestRelationships(item: unknown): Array<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"source-authoring.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/source-authoring.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
7
7
|
import { dump as dumpYaml, load as loadYaml } from "js-yaml";
|
|
8
8
|
import { isCollection, isMap, isNode, isScalar, parseDocument, stringify, } from "yaml";
|
|
9
9
|
import { OperationError } from "../../cli-errors.js";
|
|
10
|
+
import { reviseRequirementBody } from "../../entity-body-context.js";
|
|
10
11
|
import { requirementSemanticText } from "../../extractors/markdown.js";
|
|
11
12
|
import { canonicalFilesystemPath } from "../../utils/canonical-path.js";
|
|
12
13
|
import { CANONICAL_ENTITY_PATHS, isDerivedKbPath, } from "../../utils/kb-paths.js";
|
|
@@ -248,6 +249,26 @@ function sourcePath(context, input, entity, existing) {
|
|
|
248
249
|
}
|
|
249
250
|
return resolveContainedSourcePath(context.workspaceRoot, relative);
|
|
250
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* The absolute authored document an upsert of an entity the store does not
|
|
254
|
+
* hold would write: the same resolution writeSourceForUpsert uses, so a check
|
|
255
|
+
* on that file judges the file the writer merges into. Undefined when the
|
|
256
|
+
* writer has no target (it reports DOCUMENT_PATH_REQUIRED itself).
|
|
257
|
+
*/
|
|
258
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
259
|
+
export function newEntitySourceTarget(context, input) {
|
|
260
|
+
try {
|
|
261
|
+
return sourcePath(context, input, { id: input.id }, undefined);
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/** The front matter of an authored document; throws on YAML it cannot parse. */
|
|
268
|
+
// implements REQ-kibi-uncompiled-source-visibility
|
|
269
|
+
export function authoredFrontmatter(content) {
|
|
270
|
+
return bodyAndFrontmatter(content).frontmatter;
|
|
271
|
+
}
|
|
251
272
|
/**
|
|
252
273
|
* Front matter keys an upsert never writes: runtime provenance/timestamps
|
|
253
274
|
* belong to the compiled entity, not to the authored source artifact, and
|
|
@@ -347,6 +368,33 @@ function patchFrontmatter(raw, loaded, updates) {
|
|
|
347
368
|
}
|
|
348
369
|
return patched;
|
|
349
370
|
}
|
|
371
|
+
// implements REQ-kb-upsert-statement-follows-semantic-text
|
|
372
|
+
function sameStatement(left, right) {
|
|
373
|
+
const normalized = (text) => text.replace(/\s+/g, " ").trim();
|
|
374
|
+
return normalized(left) === normalized(right);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* The body an upsert without `document.body` keeps. A requirement whose
|
|
378
|
+
* `semantic_text` changes gets the new statement in place of the old one
|
|
379
|
+
* (context sections are kept), so search and readers of the document never
|
|
380
|
+
* quote a statement the requirement no longer makes.
|
|
381
|
+
*/
|
|
382
|
+
// implements REQ-kb-upsert-statement-follows-semantic-text
|
|
383
|
+
function bodyForStatement(type, entity, existing) {
|
|
384
|
+
if (type !== "req" || typeof entity.semantic_text !== "string") {
|
|
385
|
+
return existing.body;
|
|
386
|
+
}
|
|
387
|
+
const previous = typeof existing.frontmatter.semantic_text === "string"
|
|
388
|
+
? existing.frontmatter.semantic_text
|
|
389
|
+
: requirementSemanticText(existing.body);
|
|
390
|
+
if (sameStatement(previous, entity.semantic_text))
|
|
391
|
+
return existing.body;
|
|
392
|
+
const leading = /^\n*/.exec(existing.body)?.[0] || "\n\n";
|
|
393
|
+
return `${leading}${reviseRequirementBody({
|
|
394
|
+
existingBody: existing.body,
|
|
395
|
+
statement: entity.semantic_text,
|
|
396
|
+
})}`;
|
|
397
|
+
}
|
|
350
398
|
function renderEntityDocument(input, entity, existingContent) {
|
|
351
399
|
const existing = existingContent
|
|
352
400
|
? bodyAndFrontmatter(existingContent)
|
|
@@ -354,7 +402,7 @@ function renderEntityDocument(input, entity, existingContent) {
|
|
|
354
402
|
const body = input.document?.body !== undefined
|
|
355
403
|
? input.document.body
|
|
356
404
|
: existingContent !== undefined
|
|
357
|
-
? existing
|
|
405
|
+
? bodyForStatement(input.type, entity, existing)
|
|
358
406
|
: input.type === "req"
|
|
359
407
|
? `${String(entity.semantic_text ?? "").trim()}\n`
|
|
360
408
|
: "\n";
|
|
@@ -790,7 +838,7 @@ export function renderYamlRelationshipDeletion(sourcePathValue, existingContent,
|
|
|
790
838
|
* document's current content. Compile plans render their documents with it
|
|
791
839
|
* so a plan writes exactly what `kb_upsert` would.
|
|
792
840
|
*/
|
|
793
|
-
// implements REQ-kibi-change-to-proof-plan-compiler-v2
|
|
841
|
+
// implements REQ-kibi-change-to-proof-plan-compiler-v2, REQ-kb-upsert-statement-follows-semantic-text
|
|
794
842
|
export function renderSourceDocument(input, entity, existingContent, relativePath) {
|
|
795
843
|
const extension = path.extname(relativePath).toLowerCase();
|
|
796
844
|
if (extension === ".yaml" || extension === ".yml") {
|
|
@@ -9,6 +9,11 @@ export type StoredEntityLookup = {
|
|
|
9
9
|
readonly exists: () => Promise<boolean>;
|
|
10
10
|
/** The stored entity, loaded only when one exists. */
|
|
11
11
|
readonly entity: () => Promise<StoredEntity>;
|
|
12
|
+
/**
|
|
13
|
+
* The entity's own relationships when they come from its authored file
|
|
14
|
+
* rather than the store (an entity the store does not hold yet).
|
|
15
|
+
*/
|
|
16
|
+
readonly relationships?: readonly Readonly<Record<string, unknown>>[];
|
|
12
17
|
};
|
|
13
18
|
/**
|
|
14
19
|
* One read of the entity an upsert replaces, shared by every step of the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stored-entity.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/stored-entity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"stored-entity.d.ts","sourceRoot":"","sources":["../../../src/operations/mutation/stored-entity.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAGhF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7C;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC;CACvE,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EACjC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,kBAAkB,CAsBpB"}
|