archgraph-argo 0.22.0 → 0.22.1
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.
|
@@ -76,7 +76,7 @@ Never add what the graph already has. Reuse is the default; there is no reject m
|
|
|
76
76
|
<LosslessWrite>
|
|
77
77
|
Never lose stored content silently. A write must never reduce existing content unless it is lossless (merge/delta) or you explicitly acknowledge the reduction; omission never means deletion.
|
|
78
78
|
1. Structured fields merge: element testcases merge by `name`; view membership accepts a delta `{ add, remove }`; relationship attributes merge by `name`. Omitting an existing entry preserves it. Delete only with an explicit `op: "remove"` or an explicit delta remove.
|
|
79
|
-
2. Scalar text is guarded: `description`, `statement`, `document`, `name`, `view_name` are full-value fields. A
|
|
79
|
+
2. Scalar text is guarded: `description`, `statement`, `document`, `name`, `view_name` are full-value fields. A reworded line is not a loss; dropping prior lines, or genuinely losing structured tokens (commit hashes, ids, versions, dates, paths — separator/space changes do NOT count), is blocked unless you pass `acknowledgeLoss: true` (and `lossJustification` when the loss is major). Read the current value first and edit as a minimal diff.
|
|
80
80
|
3. Destructive removals (`removeElement` / `removeRelationship` / `removeView`) require `acknowledgeLoss: true` — per mutation, or once for the whole set via the top-level `acknowledgeLoss` on `applySystemArchitectureMutation`; the full removed object is appended to the NDJSON tombstone ledger `design/KG/SystemArchitecture.tombstones.ndjson` (rotated by size) for recovery.
|
|
81
81
|
4. Always read the `lossless` loss report in the response (preview and apply). It lists removed text lines / testcases / members / objects. If it is blocked, fix the mutation—do not retry blindly and do not disable the gate.
|
|
82
82
|
</LosslessWrite>
|
|
@@ -66,12 +66,38 @@ function normalizeSegments(text) {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function tokenize(segment) {
|
|
69
|
-
const
|
|
69
|
+
const normalized = String(segment === undefined || segment === null ? '' : segment).normalize('NFKC');
|
|
70
|
+
const matches = normalized.match(TOKEN_RE);
|
|
70
71
|
return matches ? matches.map(token => token.toLowerCase()) : [];
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
// A "structured identity" token whose silent loss matters: commit hashes, ids,
|
|
75
|
+
// versions, dates, path-like strings. Bare separators ("/") and short plain
|
|
76
|
+
// numbers are NOT structured — treating them so made benign edits false-block.
|
|
73
77
|
function isStructuredToken(token) {
|
|
74
|
-
|
|
78
|
+
if (!token) return false;
|
|
79
|
+
if (/^[0-9a-f]{7,40}$/i.test(token)) return true; // commit hash / long hex
|
|
80
|
+
if (/[/\\]/.test(token) && /[a-z]/i.test(token)) return true; // path-like (has a letter)
|
|
81
|
+
if (/[a-z]/i.test(token) && /\d/.test(token) && /[-_]/.test(token)) return true; // id-like AT-rules-04
|
|
82
|
+
if (/^[a-z]*\d+(?:\.\d+)+$/i.test(token)) return true; // version-like stix2.1 / v2.1 / 1.2.3
|
|
83
|
+
if (/^\d{4}-\d{2}(?:-\d{2})?$/.test(token)) return true; // date-like 2026-09-15
|
|
84
|
+
if (/^\d{5,}$/.test(token)) return true; // long numeric id
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// NFKC + strip all whitespace + lowercase: tolerant haystack for the "is this
|
|
89
|
+
// structured token still present anywhere in the new text?" substring check.
|
|
90
|
+
// This drops false positives from separator joins (769465c:bc7f418) and spacing
|
|
91
|
+
// (STIX2.1 -> STIX 2.1) while still catching a genuinely removed token.
|
|
92
|
+
function normalizeForTokenSearch(value) {
|
|
93
|
+
return String(value === undefined || value === null ? '' : value).normalize('NFKC').replace(/\s+/g, '').toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Separator-insensitive form: also drop the punctuation that joins structured
|
|
97
|
+
// tokens, so "A/B" -> "A、B" or "v2.1" -> "v2 1" is still recognised as the same
|
|
98
|
+
// token (same content, punctuation changed) and not mistaken for a loss.
|
|
99
|
+
function comparableTokenForm(value) {
|
|
100
|
+
return normalizeForTokenSearch(value).replace(/[/\\:.\-_]/g, '');
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
// Sørensen–Dice coefficient over token sets (deterministic, no network).
|
|
@@ -97,7 +123,7 @@ function detectTextLoss(oldText, newText) {
|
|
|
97
123
|
}
|
|
98
124
|
const newSegments = normalizeSegments(newText);
|
|
99
125
|
const newNormalized = normalizeForCompare(newText);
|
|
100
|
-
const
|
|
126
|
+
const newHaystack = comparableTokenForm(newText);
|
|
101
127
|
const newSegmentTokens = newSegments.map(tokenize);
|
|
102
128
|
const removedSegments = [];
|
|
103
129
|
const modifiedSegments = [];
|
|
@@ -105,7 +131,7 @@ function detectTextLoss(oldText, newText) {
|
|
|
105
131
|
for (const segment of oldSegments) {
|
|
106
132
|
const segmentTokens = tokenize(segment);
|
|
107
133
|
for (const token of segmentTokens) {
|
|
108
|
-
if (isStructuredToken(token) && !
|
|
134
|
+
if (isStructuredToken(token) && !newHaystack.includes(comparableTokenForm(token))) structuredTokensRemoved.push(token);
|
|
109
135
|
}
|
|
110
136
|
if (newNormalized.includes(segment)) continue; // kept verbatim
|
|
111
137
|
const best = newSegmentTokens.reduce((max, tokens) => Math.max(max, diceCoefficient(segmentTokens, tokens)), 0);
|
package/package.json
CHANGED