hive-lite 0.1.6 → 0.1.8
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 +11 -11
- package/docs/skills/hive-lite-map-maintainer/references/lifecycle.md +1 -1
- package/docs/skills/hive-lite-map-maintainer/references/repair-rules.md +4 -0
- package/docs/skills/hive-lite-start-prompt/SKILL.md +11 -2
- package/package.json +2 -2
- package/src/cli.js +42 -0
- package/src/lib/change.js +75 -1
- package/src/lib/context.js +420 -5
- package/src/lib/evidence.js +7 -0
- package/src/lib/map.js +63 -0
- package/src/lib/risk.js +5 -0
- package/src/lib/scope.js +117 -2
package/README.md
CHANGED
|
@@ -74,9 +74,9 @@ node bin/hive.js next --agent codex --json
|
|
|
74
74
|
`status` and `next` show actionable split notes by default: active split notes with accepted phase progress, or the newest unstarted split note when no active split exists. Use `status --all` to inspect every runtime split note. `next` may recommend start, finish, map maintenance, split continuation, dirty-worktree cleanup, or git repo setup. It does not run agents, commit, accept risk, or modify files.
|
|
75
75
|
In JSON output, `splitNoteSummary.primaryRecentSplitNote` identifies the split note that `status`/`next` treat as primary, and `splitNoteSummary.otherActiveSplitNotes` lists older active split notes with accepted progress.
|
|
76
76
|
|
|
77
|
-
When you pass `next --agent <codex|claude|gemini>` or `next --path <skills-dir>`, `next` also checks whether the recommended Hive Lite operator skill is
|
|
77
|
+
When you pass `next --agent <codex|claude|gemini>` or `next --path <skills-dir>`, `next` also checks whether the recommended Hive Lite operator skill is current in that target. If it is missing or stale, `next` recommends `skills sync` before the handoff.
|
|
78
78
|
|
|
79
|
-
###
|
|
79
|
+
### Sync Agent Skills
|
|
80
80
|
|
|
81
81
|
Hive Lite ships four operator skills:
|
|
82
82
|
|
|
@@ -98,22 +98,22 @@ node bin/hive.js skills doctor --json
|
|
|
98
98
|
|
|
99
99
|
Codex, Claude, and Gemini targets are global user-skill installs. `--agent codex` writes to `~/.codex/skills`, `--agent claude` writes to `~/.claude/skills`, and `--agent gemini` writes to `~/.gemini/skills`. They do not create or update repo-local `.codex/skills`, `.claude/skills`, or `.gemini/skills`. Use `--path <repo-local-skills-dir>` only when you intentionally want a custom repo-local copy that Hive Lite can inspect.
|
|
100
100
|
|
|
101
|
-
Install
|
|
101
|
+
Install or update skills in one step:
|
|
102
102
|
|
|
103
103
|
```bash
|
|
104
|
-
node bin/hive.js skills
|
|
105
|
-
node bin/hive.js skills
|
|
106
|
-
node bin/hive.js skills
|
|
107
|
-
node bin/hive.js skills
|
|
104
|
+
node bin/hive.js skills sync --agent codex
|
|
105
|
+
node bin/hive.js skills sync --agent claude
|
|
106
|
+
node bin/hive.js skills sync --agent gemini
|
|
107
|
+
node bin/hive.js skills sync --agent all
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
`
|
|
110
|
+
`sync` installs missing skills and overwrites stale copies with the bundled version.
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
`install` is still available for conservative installs that should not overwrite stale local copies unless `--force` is passed:
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
|
-
node bin/hive.js skills
|
|
116
|
-
node bin/hive.js skills
|
|
115
|
+
node bin/hive.js skills install --agent codex
|
|
116
|
+
node bin/hive.js skills install --agent all
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
Use `--dry-run` to preview writes. Use `--path <skills-dir>` for a custom target.
|
|
@@ -56,7 +56,7 @@ Use when a real requirement hits a map gap during start:
|
|
|
56
56
|
|
|
57
57
|
- `find.mode` is `needs_map` or `discovery_context`.
|
|
58
58
|
- `find` selected a generic/broad area.
|
|
59
|
-
- warnings include `NO_CONFIDENT_AREA`, `MISSING_DIRECT_WRITABLE_SCOPE`, `MISSING_ENTRYPOINT`, or `MISSING_VALIDATION`.
|
|
59
|
+
- warnings include `NO_CONFIDENT_AREA`, `MISSING_DIRECT_WRITABLE_SCOPE`, `MISSING_DIRECT_NEW_FILE_SCOPE`, `DIRECT_ONLY_REFERENCE_PEERS`, `BROAD_FALLBACK_ONLY`, `TARGET_ENTITY_MISMATCH`, `MISSING_ARTIFACT_FAMILY_SCOPE`, `MISSING_REQUIRED_HOOK_SCOPE`, `MISSING_ENTRYPOINT`, or `MISSING_VALIDATION`.
|
|
60
60
|
- `map health --area <selected>` shows critical findings.
|
|
61
61
|
|
|
62
62
|
Goal:
|
|
@@ -55,6 +55,10 @@ Rules:
|
|
|
55
55
|
|
|
56
56
|
- `readable` may be broad.
|
|
57
57
|
- `writable_direct` should be exact existing files, usually 2-8 files.
|
|
58
|
+
- Prefer `writable_existing` for exact update targets and `writable_create_patterns` for new file permissions when the repo has repeated artifact families.
|
|
59
|
+
- For intents that create new peer files, use very small direct patterns that match only the expected family, such as `src/providers/*Provider.ts`; do not fall back to an entire source tree.
|
|
60
|
+
- Existing peer files that are only copy/reference examples should stay readable/reference context, not selected Direct Writable for a new peer target.
|
|
61
|
+
- Put peer examples under `readable_reference` and describe repeated families under `artifact_families` when possible.
|
|
58
62
|
- Broad patterns such as `apps/*/src/**`, `packages/**`, `server/**`, and `**` must not be in `writable_direct`.
|
|
59
63
|
- Every conditional/fallback item needs `reason` and `requires_review: true`.
|
|
60
64
|
|
|
@@ -180,8 +180,9 @@ Accept the packet only when all are true:
|
|
|
180
180
|
- `mode` is `edit_context`.
|
|
181
181
|
- `area.id` is present.
|
|
182
182
|
- `writableScope` contains at least one narrow direct file.
|
|
183
|
+
- `writePlan.blockingWarnings` is empty when present.
|
|
183
184
|
- `validationPlan` is not empty.
|
|
184
|
-
- `warnings` has no map-gap warning such as `NO_CONFIDENT_AREA`, `MISSING_DIRECT_WRITABLE_SCOPE`, `MISSING_ENTRYPOINT`, or `MISSING_VALIDATION`.
|
|
185
|
+
- `warnings` has no map-gap warning such as `NO_CONFIDENT_AREA`, `MISSING_DIRECT_WRITABLE_SCOPE`, `MISSING_DIRECT_NEW_FILE_SCOPE`, `DIRECT_ONLY_REFERENCE_PEERS`, `BROAD_FALLBACK_ONLY`, `TARGET_ENTITY_MISMATCH`, `MISSING_ENTRYPOINT`, or `MISSING_VALIDATION`.
|
|
185
186
|
- `hive-lite map health --area <areaId> --json` reports no critical findings.
|
|
186
187
|
|
|
187
188
|
Review-gated conditional or broad fallback scope is allowed. Include it in the final prompt as a boundary, not as a failure.
|
|
@@ -200,7 +201,12 @@ Repair principles:
|
|
|
200
201
|
|
|
201
202
|
- Prefer focused product/work area ids such as `dashboard.action_inbox`, not one-part ids such as `dashboard`.
|
|
202
203
|
- Preserve unrelated areas exactly unless a change is needed for the reported map gap.
|
|
203
|
-
- Use `scope.writable_direct` for exact files the coding agent may edit.
|
|
204
|
+
- Use `scope.writable_direct` for exact files the coding agent may edit, or very small direct patterns when the intent needs new peer files such as a provider, proxy, adapter, connector, or plugin.
|
|
205
|
+
- Prefer `scope.writable_existing` for exact update targets and `scope.writable_create_patterns` for narrow create permissions when the map needs operation-specific write capabilities.
|
|
206
|
+
- Put copy sources and peer examples under `scope.readable_reference` when they should guide implementation but not be edited.
|
|
207
|
+
- Use `artifact_families` to declare required create artifacts and hook files for repeated artifact families such as providers, adapters, connectors, endpoints, DTOs, tests, or migrations.
|
|
208
|
+
- Treat Direct Writable as the selected intent-specific write target list, not the area's full writable maximum.
|
|
209
|
+
- Keep peer examples, entrypoints, and similar existing files as reference context unless the current intent specifically requires editing them.
|
|
204
210
|
- Put conditional files under `scope.writable_conditional` with `reason` and `requires_review: true`.
|
|
205
211
|
- Put broad patterns only under `scope.writable_broad_fallback` with `reason` and `requires_review: true`.
|
|
206
212
|
- Never put broad patterns such as `apps/*/src/**`, `packages/**`, `server/**`, or `**` in `writable_direct`.
|
|
@@ -270,6 +276,9 @@ Use this context packet as your source of truth. Edit only Direct Writable files
|
|
|
270
276
|
Direct Writable:
|
|
271
277
|
- <file>
|
|
272
278
|
|
|
279
|
+
Reference Files:
|
|
280
|
+
- <file and reason, only when useful; do not edit unless it is also Direct Writable>
|
|
281
|
+
|
|
273
282
|
Review-Gated:
|
|
274
283
|
- <conditional/broad item and reason>
|
|
275
284
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -230,6 +230,22 @@ function printContext(result) {
|
|
|
230
230
|
console.log(` risk: ${item.risk}`);
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
+
if (packet.writePlan && packet.writePlan.hypotheses && packet.writePlan.hypotheses.length > 0) {
|
|
234
|
+
console.log('');
|
|
235
|
+
console.log('Write Plan:');
|
|
236
|
+
for (const item of packet.writePlan.hypotheses) {
|
|
237
|
+
console.log(` - ${item.action} ${item.artifactFamily}: ${item.operation}${item.targetIdentity ? ` for ${item.targetIdentity}` : ''}`);
|
|
238
|
+
}
|
|
239
|
+
if (packet.writePlan.blockingWarnings && packet.writePlan.blockingWarnings.length > 0) {
|
|
240
|
+
console.log(' blocking:');
|
|
241
|
+
for (const item of packet.writePlan.blockingWarnings) console.log(` - ${item.code}: ${item.message}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (packet.writePlan && packet.writePlan.referenceFiles && packet.writePlan.referenceFiles.length > 0) {
|
|
245
|
+
console.log('');
|
|
246
|
+
console.log('Reference Files:');
|
|
247
|
+
for (const file of packet.writePlan.referenceFiles) console.log(` - ${file.path}\n reason: ${file.reason}`);
|
|
248
|
+
}
|
|
233
249
|
console.log('');
|
|
234
250
|
console.log('Relevant Files:');
|
|
235
251
|
if (packet.relevantFiles.length === 0) console.log(' - (none found)');
|
|
@@ -242,6 +258,14 @@ function printContext(result) {
|
|
|
242
258
|
console.log('');
|
|
243
259
|
console.log(`Scope Quality: ${packet.scope.quality}`);
|
|
244
260
|
console.log('');
|
|
261
|
+
console.log('Writable Existing Scope:');
|
|
262
|
+
if (!packet.scope.writableExisting || packet.scope.writableExisting.length === 0) console.log(' - (none configured)');
|
|
263
|
+
for (const item of packet.scope.writableExisting || []) console.log(` - ${patternDisplay(item)}`);
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log('Writable Create Patterns:');
|
|
266
|
+
if (!packet.scope.writableCreatePatterns || packet.scope.writableCreatePatterns.length === 0) console.log(' - (none configured)');
|
|
267
|
+
for (const item of packet.scope.writableCreatePatterns || []) console.log(` - ${patternDisplay(item)}`);
|
|
268
|
+
console.log('');
|
|
245
269
|
console.log('Conditional Writable Scope:');
|
|
246
270
|
if (packet.scope.writableConditional.length === 0) console.log(' - (none configured)');
|
|
247
271
|
for (const item of packet.scope.writableConditional) console.log(` - ${patternDisplay(item)}`);
|
|
@@ -363,6 +387,24 @@ function printCheck(root, result) {
|
|
|
363
387
|
console.log(' broad fallback:');
|
|
364
388
|
for (const item of change.scope.writableBroadFallback) console.log(` - ${patternDisplay(item)}`);
|
|
365
389
|
}
|
|
390
|
+
if (change.writePlanStatus) {
|
|
391
|
+
console.log('');
|
|
392
|
+
console.log('Write Plan:');
|
|
393
|
+
console.log(` ${change.writePlanStatus.status}`);
|
|
394
|
+
if (change.writePlanStatus.contextMode) console.log(` context mode: ${change.writePlanStatus.contextMode}`);
|
|
395
|
+
if (change.writePlanStatus.selectedWritableDirect && change.writePlanStatus.selectedWritableDirect.length > 0) {
|
|
396
|
+
console.log(' selected direct:');
|
|
397
|
+
for (const item of change.writePlanStatus.selectedWritableDirect) console.log(` - ${item}`);
|
|
398
|
+
}
|
|
399
|
+
if (change.writePlanStatus.referenceOnlyTouched && change.writePlanStatus.referenceOnlyTouched.length > 0) {
|
|
400
|
+
console.log(' reference-only touched:');
|
|
401
|
+
for (const item of change.writePlanStatus.referenceOnlyTouched) console.log(` - ${item.path}: ${item.reason || item.reference}`);
|
|
402
|
+
}
|
|
403
|
+
if (change.writePlanStatus.blockingReasons && change.writePlanStatus.blockingReasons.length > 0) {
|
|
404
|
+
console.log(' blockers:');
|
|
405
|
+
for (const reason of change.writePlanStatus.blockingReasons) console.log(` - ${reason}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
366
408
|
console.log('');
|
|
367
409
|
console.log('Validation:');
|
|
368
410
|
console.log(` ${change.validation.status}`);
|
package/src/lib/change.js
CHANGED
|
@@ -174,9 +174,15 @@ function scopeFromContext(root, context) {
|
|
|
174
174
|
});
|
|
175
175
|
}
|
|
176
176
|
const scope = context.scope;
|
|
177
|
+
const selectedDirect = Array.isArray(context.writableScope) && context.writableScope.length > 0
|
|
178
|
+
? context.writableScope
|
|
179
|
+
: (scope.writableDirect || []);
|
|
177
180
|
return {
|
|
178
181
|
readable: (scope.readable || []).slice(),
|
|
179
|
-
|
|
182
|
+
readableReference: (scope.readableReference || []).slice(),
|
|
183
|
+
writableDirect: selectedDirect.map(itemPattern).filter(Boolean),
|
|
184
|
+
writableExisting: normalizePatternList(scope.writableExisting || [], { source: 'writable_existing' }),
|
|
185
|
+
writableCreatePatterns: normalizePatternList(scope.writableCreatePatterns || [], { source: 'writable_create_patterns' }),
|
|
180
186
|
writableConditional: normalizePatternList(scope.writableConditional || [], { requiresReview: true }),
|
|
181
187
|
writableBroadFallback: normalizePatternList(scope.writableBroadFallback || [], { requiresReview: true }),
|
|
182
188
|
forbidden: (scope.forbidden || []).slice(),
|
|
@@ -190,6 +196,9 @@ function firstItemMatch(file, items) {
|
|
|
190
196
|
|
|
191
197
|
function scopeCheck(root, files, context, map) {
|
|
192
198
|
const filePaths = files.map((item) => item.path || item);
|
|
199
|
+
const referenceOnly = context && context.writePlan && Array.isArray(context.writePlan.referenceFiles)
|
|
200
|
+
? context.writePlan.referenceFiles.map((item) => item.path || item.pattern).filter(Boolean)
|
|
201
|
+
: [];
|
|
193
202
|
const matchedAreas = [];
|
|
194
203
|
let scope = {
|
|
195
204
|
readable: [],
|
|
@@ -236,6 +245,13 @@ function scopeCheck(root, files, context, map) {
|
|
|
236
245
|
violations.push(`${file} matched doNotTouch ${forbidden}`);
|
|
237
246
|
continue;
|
|
238
247
|
}
|
|
248
|
+
const reference = firstPatternMatch(file, referenceOnly);
|
|
249
|
+
if (reference) {
|
|
250
|
+
matchedTiers.unmatched.push(file);
|
|
251
|
+
review.push(file);
|
|
252
|
+
reviewDetails.push(`${file} matched reference-only file ${reference}`);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
239
255
|
const direct = firstItemMatch(file, scope.writableDirect);
|
|
240
256
|
if (direct) {
|
|
241
257
|
matchedTiers.direct.push(file);
|
|
@@ -298,6 +314,63 @@ function validationPlanFromContextOrMap(context, map) {
|
|
|
298
314
|
}] : [];
|
|
299
315
|
}
|
|
300
316
|
|
|
317
|
+
function evaluateWritePlanStatus(files, context) {
|
|
318
|
+
if (!context) {
|
|
319
|
+
return {
|
|
320
|
+
status: 'not_available',
|
|
321
|
+
contextMode: null,
|
|
322
|
+
blockingReasons: [],
|
|
323
|
+
selectedWritableDirect: [],
|
|
324
|
+
referenceOnlyTouched: [],
|
|
325
|
+
requiredWrites: [],
|
|
326
|
+
blockingWarnings: [],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const writePlan = context.writePlan || null;
|
|
331
|
+
const blockingWarnings = writePlan && Array.isArray(writePlan.blockingWarnings)
|
|
332
|
+
? writePlan.blockingWarnings
|
|
333
|
+
: [];
|
|
334
|
+
const selectedWritableDirect = Array.isArray(context.writableScope) ? context.writableScope : [];
|
|
335
|
+
const changedPaths = files.map((item) => item.path || item);
|
|
336
|
+
const referenceFiles = writePlan && Array.isArray(writePlan.referenceFiles) ? writePlan.referenceFiles : [];
|
|
337
|
+
const referenceOnlyTouched = [];
|
|
338
|
+
for (const file of changedPaths) {
|
|
339
|
+
const reference = referenceFiles.find((item) => matchesPattern(file, item.path || item.pattern));
|
|
340
|
+
if (reference) {
|
|
341
|
+
referenceOnlyTouched.push({
|
|
342
|
+
path: file,
|
|
343
|
+
reference: reference.path || reference.pattern,
|
|
344
|
+
reason: reference.reason || '',
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const blockingReasons = [];
|
|
350
|
+
if (context.mode && context.mode !== 'edit_context') {
|
|
351
|
+
blockingReasons.push(`context mode is ${context.mode}, not edit_context`);
|
|
352
|
+
}
|
|
353
|
+
for (const warning of blockingWarnings) {
|
|
354
|
+
blockingReasons.push(`${warning.code}: ${warning.message}`);
|
|
355
|
+
}
|
|
356
|
+
for (const item of referenceOnlyTouched) {
|
|
357
|
+
blockingReasons.push(`${item.path} touched reference-only file ${item.reference}`);
|
|
358
|
+
}
|
|
359
|
+
if (selectedWritableDirect.length === 0 && changedPaths.length > 0) {
|
|
360
|
+
blockingReasons.push('context has no selected Direct Writable scope');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return {
|
|
364
|
+
status: blockingReasons.length > 0 ? 'blocked' : 'clean',
|
|
365
|
+
contextMode: context.mode || null,
|
|
366
|
+
blockingReasons: [...new Set(blockingReasons)],
|
|
367
|
+
selectedWritableDirect,
|
|
368
|
+
referenceOnlyTouched,
|
|
369
|
+
requiredWrites: writePlan && Array.isArray(writePlan.hypotheses) ? writePlan.hypotheses : [],
|
|
370
|
+
blockingWarnings,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
301
374
|
function createOrUpdateChange(cwd, options = {}) {
|
|
302
375
|
const root = repoRoot(cwd);
|
|
303
376
|
const map = loadProjectMap(root);
|
|
@@ -339,6 +412,7 @@ function createOrUpdateChange(cwd, options = {}) {
|
|
|
339
412
|
text: diffText,
|
|
340
413
|
},
|
|
341
414
|
scope: scopeCheck(root, files, context, map),
|
|
415
|
+
writePlanStatus: evaluateWritePlanStatus(files, context),
|
|
342
416
|
validation: {
|
|
343
417
|
status: existing && existing.validation ? existing.validation.status : 'not_run',
|
|
344
418
|
plan: validationPlanFromContextOrMap(context, map),
|
package/src/lib/context.js
CHANGED
|
@@ -40,6 +40,374 @@ function tokenize(value) {
|
|
|
40
40
|
return [...new Set([...latin, ...cjk])];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function unique(values) {
|
|
44
|
+
return [...new Set((values || []).filter(Boolean))];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function looksLikeFilePattern(pattern) {
|
|
48
|
+
if (String(pattern || '').includes('*')) return false;
|
|
49
|
+
const base = path.basename(String(pattern || ''));
|
|
50
|
+
return /\.[A-Za-z0-9]+$/.test(base);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isCreateCapablePattern(pattern) {
|
|
54
|
+
return !looksLikeFilePattern(pattern);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const IDENTITY_ALIASES = {
|
|
58
|
+
openai: ['openai', 'chatgpt', 'gpt'],
|
|
59
|
+
google: ['google', 'gemini'],
|
|
60
|
+
qwen: ['qwen', 'tongyi'],
|
|
61
|
+
wan: ['wan'],
|
|
62
|
+
aliyun: ['aliyun', 'ali'],
|
|
63
|
+
anthropic: ['anthropic', 'claude'],
|
|
64
|
+
azure: ['azure'],
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
function canonicalIdentity(value) {
|
|
68
|
+
const text = normalize(value);
|
|
69
|
+
if (!text) return null;
|
|
70
|
+
for (const [canonical, aliases] of Object.entries(IDENTITY_ALIASES)) {
|
|
71
|
+
if (aliases.includes(text)) return canonical;
|
|
72
|
+
}
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function targetIdentityForIntent(intent) {
|
|
77
|
+
const tokens = tokenize(intent);
|
|
78
|
+
for (const token of tokens) {
|
|
79
|
+
const identity = canonicalIdentity(token);
|
|
80
|
+
if (identity && Object.prototype.hasOwnProperty.call(IDENTITY_ALIASES, identity)) return identity;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function peerIdentityFromPath(pattern) {
|
|
86
|
+
if (!looksLikeFilePattern(pattern)) return null;
|
|
87
|
+
const tokens = basenameTokens(pattern);
|
|
88
|
+
for (const token of tokens) {
|
|
89
|
+
const identity = canonicalIdentity(token);
|
|
90
|
+
if (identity && Object.prototype.hasOwnProperty.call(IDENTITY_ALIASES, identity)) return identity;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function identityMatches(left, right) {
|
|
96
|
+
if (!left || !right) return false;
|
|
97
|
+
return canonicalIdentity(left) === canonicalIdentity(right);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function intentTargetsAllPeers(intent) {
|
|
101
|
+
const text = normalize(intent);
|
|
102
|
+
return [
|
|
103
|
+
/\b(all|every|each)\b/,
|
|
104
|
+
/所有|全部|每个|统一|全量|全部/,
|
|
105
|
+
].some((pattern) => pattern.test(text));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function arrayFrom(value) {
|
|
109
|
+
if (Array.isArray(value)) return value.map((item) => String(item || '')).filter(Boolean);
|
|
110
|
+
if (value == null || value === '') return [];
|
|
111
|
+
if (typeof value === 'object') return [];
|
|
112
|
+
return [String(value)];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function configuredArtifactFamilies(area) {
|
|
116
|
+
const raw = area && area.artifact_families ? area.artifact_families : null;
|
|
117
|
+
if (!raw) return [];
|
|
118
|
+
if (Array.isArray(raw)) {
|
|
119
|
+
return raw.map((item) => ({
|
|
120
|
+
id: item.id || item.name || '',
|
|
121
|
+
triggerTerms: [
|
|
122
|
+
...arrayFrom(item.trigger_terms || item.triggerTerms),
|
|
123
|
+
...arrayFrom(item.trigger_terms && item.trigger_terms.en),
|
|
124
|
+
...arrayFrom(item.trigger_terms && item.trigger_terms.zh),
|
|
125
|
+
],
|
|
126
|
+
createRequires: arrayFrom(item.create_requires || item.createRequires),
|
|
127
|
+
hooks: arrayFrom(item.hooks),
|
|
128
|
+
examples: arrayFrom(item.examples),
|
|
129
|
+
})).filter((item) => item.id);
|
|
130
|
+
}
|
|
131
|
+
return Object.entries(raw).map(([id, value]) => ({
|
|
132
|
+
id,
|
|
133
|
+
triggerTerms: [
|
|
134
|
+
...arrayFrom(value && (value.trigger_terms || value.triggerTerms)),
|
|
135
|
+
...arrayFrom(value && value.trigger_terms && value.trigger_terms.en),
|
|
136
|
+
...arrayFrom(value && value.trigger_terms && value.trigger_terms.zh),
|
|
137
|
+
],
|
|
138
|
+
createRequires: arrayFrom(value && (value.create_requires || value.createRequires)),
|
|
139
|
+
hooks: arrayFrom(value && value.hooks),
|
|
140
|
+
examples: arrayFrom(value && value.examples),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function configuredArtifactFamilyForIntent(area, intent) {
|
|
145
|
+
const text = normalize(intent);
|
|
146
|
+
for (const family of configuredArtifactFamilies(area)) {
|
|
147
|
+
const terms = unique([
|
|
148
|
+
family.id,
|
|
149
|
+
...family.triggerTerms,
|
|
150
|
+
]).map(normalize).filter(Boolean);
|
|
151
|
+
if (terms.some((term) => text.includes(term))) return family;
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function classifyArtifactFamily(intent, area) {
|
|
157
|
+
const configured = configuredArtifactFamilyForIntent(area, intent);
|
|
158
|
+
if (configured) return configured.id;
|
|
159
|
+
const text = normalize(intent);
|
|
160
|
+
if ([
|
|
161
|
+
/\b(provider|proxy|adapter|integration|connector|plugin)\b/,
|
|
162
|
+
/代理|提供商|适配器|集成|连接器|插件/,
|
|
163
|
+
].some((pattern) => pattern.test(text))) return 'provider_proxy';
|
|
164
|
+
if ([
|
|
165
|
+
/\b(endpoint|controller|route|api)\b/,
|
|
166
|
+
/接口|端点|路由|控制器/,
|
|
167
|
+
].some((pattern) => pattern.test(text))) return 'endpoint';
|
|
168
|
+
if ([
|
|
169
|
+
/\b(dto|request|response|payload)\b/,
|
|
170
|
+
/请求|响应|入参|出参/,
|
|
171
|
+
].some((pattern) => pattern.test(text))) return 'dto';
|
|
172
|
+
if ([
|
|
173
|
+
/\b(migration|database|db|sql)\b/,
|
|
174
|
+
/数据库|迁移|表结构/,
|
|
175
|
+
].some((pattern) => pattern.test(text))) return 'migration';
|
|
176
|
+
if ([
|
|
177
|
+
/\b(test|spec)\b/,
|
|
178
|
+
/测试|用例/,
|
|
179
|
+
].some((pattern) => pattern.test(text))) return 'test';
|
|
180
|
+
if ([
|
|
181
|
+
/\b(component|page|view|ui)\b/,
|
|
182
|
+
/组件|页面|视图|界面/,
|
|
183
|
+
].some((pattern) => pattern.test(text))) return 'component';
|
|
184
|
+
if ([
|
|
185
|
+
/\b(service)\b/,
|
|
186
|
+
/服务/,
|
|
187
|
+
].some((pattern) => pattern.test(text))) return 'service';
|
|
188
|
+
return 'unknown';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function classifyIntentWrite(intent, area) {
|
|
192
|
+
const text = normalize(intent);
|
|
193
|
+
const createTerm = [
|
|
194
|
+
/\b(add|create|new|introduce|support|implement)\b/,
|
|
195
|
+
/新增|新建|创建|添加|接入|支持/,
|
|
196
|
+
].some((pattern) => pattern.test(text));
|
|
197
|
+
const deleteTerm = [
|
|
198
|
+
/\b(delete|remove)\b/,
|
|
199
|
+
/删除|移除/,
|
|
200
|
+
].some((pattern) => pattern.test(text));
|
|
201
|
+
const renameTerm = [
|
|
202
|
+
/\b(rename|move)\b/,
|
|
203
|
+
/重命名|改名|迁移/,
|
|
204
|
+
].some((pattern) => pattern.test(text));
|
|
205
|
+
const targetAll = intentTargetsAllPeers(intent);
|
|
206
|
+
const artifactFamily = classifyArtifactFamily(intent, area);
|
|
207
|
+
const familyConfig = configuredArtifactFamilies(area).find((item) => item.id === artifactFamily) || null;
|
|
208
|
+
const targetIdentity = targetIdentityForIntent(intent);
|
|
209
|
+
let action = 'modify';
|
|
210
|
+
if (deleteTerm) action = 'delete';
|
|
211
|
+
else if (renameTerm) action = 'rename';
|
|
212
|
+
else if (createTerm && !targetAll) action = 'create';
|
|
213
|
+
return {
|
|
214
|
+
action,
|
|
215
|
+
artifactFamily,
|
|
216
|
+
targetIdentity,
|
|
217
|
+
operation: action === 'create' && artifactFamily === 'provider_proxy' ? 'create_file' : 'modify_existing_file',
|
|
218
|
+
required: true,
|
|
219
|
+
targetAll,
|
|
220
|
+
intentKind: action === 'create' ? `add_${artifactFamily}` : `${action}_${artifactFamily}`,
|
|
221
|
+
createRequires: familyConfig ? familyConfig.createRequires : [],
|
|
222
|
+
hooks: familyConfig ? familyConfig.hooks : [],
|
|
223
|
+
confidence: artifactFamily === 'unknown' ? 'low' : 'medium',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function directCapabilities(scope) {
|
|
228
|
+
const explicitItems = [
|
|
229
|
+
...(scope.writableDirectItems || []),
|
|
230
|
+
];
|
|
231
|
+
const items = explicitItems.length
|
|
232
|
+
? explicitItems
|
|
233
|
+
: (scope.writableDirect || []).map((pattern) => ({ pattern, source: 'writable_direct' }));
|
|
234
|
+
const seen = new Set();
|
|
235
|
+
return items.map((item) => {
|
|
236
|
+
const pattern = item.pattern || item.path || item;
|
|
237
|
+
const createCapable = isCreateCapablePattern(pattern);
|
|
238
|
+
const configuredOperations = arrayFrom(item.operations);
|
|
239
|
+
const operations = configuredOperations.length
|
|
240
|
+
? configuredOperations
|
|
241
|
+
: (createCapable ? ['update_existing', 'create_file'] : ['update_existing']);
|
|
242
|
+
const key = `${pattern}:${operations.join(',')}:${item.source || ''}:${item.artifact || ''}`;
|
|
243
|
+
if (seen.has(key)) return null;
|
|
244
|
+
seen.add(key);
|
|
245
|
+
return {
|
|
246
|
+
scope: pattern,
|
|
247
|
+
operations,
|
|
248
|
+
breadth: createCapable ? 'narrow_pattern' : 'exact',
|
|
249
|
+
reviewGated: false,
|
|
250
|
+
source: item.source || 'writable_direct',
|
|
251
|
+
artifact: item.artifact || null,
|
|
252
|
+
artifactFamily: item.artifactFamily || item.artifact_family || null,
|
|
253
|
+
intentKinds: arrayFrom(item.intentKinds || item.intent_kinds),
|
|
254
|
+
requiredWhen: arrayFrom(item.requiredWhen || item.required_when),
|
|
255
|
+
targetSlot: item.targetSlot || item.target_slot || null,
|
|
256
|
+
peerIdentity: canonicalIdentity(item.targetIdentity || item.target_identity || item.peerIdentity || item.peer_identity) || peerIdentityFromPath(pattern),
|
|
257
|
+
};
|
|
258
|
+
}).filter(Boolean);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function warning(code, message, data = {}) {
|
|
262
|
+
return {
|
|
263
|
+
code,
|
|
264
|
+
message,
|
|
265
|
+
blocking: data.blocking !== false,
|
|
266
|
+
...data,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function buildReference(capability, reason) {
|
|
271
|
+
return {
|
|
272
|
+
path: capability.scope || capability.path,
|
|
273
|
+
role: capability.role || 'peer_example',
|
|
274
|
+
writable: false,
|
|
275
|
+
reason,
|
|
276
|
+
artifact: capability.artifact || null,
|
|
277
|
+
peerIdentity: capability.peerIdentity || null,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function artifactMatches(capability, artifact) {
|
|
282
|
+
if (!artifact) return false;
|
|
283
|
+
return capability.artifact === artifact || capability.artifactFamily === artifact;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function capabilityMatchesIntent(capability, hypothesis) {
|
|
287
|
+
if (!capability.intentKinds || capability.intentKinds.length === 0) return true;
|
|
288
|
+
return capability.intentKinds.includes(hypothesis.intentKind);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function buildWritePlan(scope, intent, area) {
|
|
292
|
+
const hypothesis = classifyIntentWrite(intent, area);
|
|
293
|
+
const capabilities = directCapabilities(scope);
|
|
294
|
+
const warnings = [];
|
|
295
|
+
const references = (scope.readableReference || []).map((item) => buildReference(
|
|
296
|
+
{
|
|
297
|
+
path: item.path,
|
|
298
|
+
role: item.role || 'reference',
|
|
299
|
+
artifact: item.artifact || null,
|
|
300
|
+
peerIdentity: canonicalIdentity(item.peerIdentity) || peerIdentityFromPath(item.path),
|
|
301
|
+
},
|
|
302
|
+
item.reason || 'readable reference from Project Map'
|
|
303
|
+
));
|
|
304
|
+
let selected = capabilities.map((item) => item.scope);
|
|
305
|
+
|
|
306
|
+
if (hypothesis.operation === 'create_file' && hypothesis.artifactFamily === 'provider_proxy') {
|
|
307
|
+
const createCapabilities = capabilities.filter((item) => (
|
|
308
|
+
item.operations.includes('create_file') && capabilityMatchesIntent(item, hypothesis)
|
|
309
|
+
));
|
|
310
|
+
const genericExistingCapabilities = capabilities.filter((item) => (
|
|
311
|
+
!item.peerIdentity && !item.operations.includes('create_file')
|
|
312
|
+
));
|
|
313
|
+
const referencePeers = capabilities.filter((item) => (
|
|
314
|
+
item.peerIdentity
|
|
315
|
+
&& hypothesis.targetIdentity
|
|
316
|
+
&& !identityMatches(item.peerIdentity, hypothesis.targetIdentity)
|
|
317
|
+
));
|
|
318
|
+
const selectedCreateCapabilities = hypothesis.createRequires.length
|
|
319
|
+
? createCapabilities.filter((item) => hypothesis.createRequires.some((artifact) => artifactMatches(item, artifact)))
|
|
320
|
+
: createCapabilities;
|
|
321
|
+
const hookCapabilities = hypothesis.hooks.length
|
|
322
|
+
? genericExistingCapabilities.filter((item) => hypothesis.hooks.some((artifact) => artifactMatches(item, artifact)))
|
|
323
|
+
: genericExistingCapabilities;
|
|
324
|
+
|
|
325
|
+
selected = selectedCreateCapabilities.length > 0
|
|
326
|
+
? [...selectedCreateCapabilities, ...hookCapabilities].map((item) => item.scope)
|
|
327
|
+
: [];
|
|
328
|
+
for (const capability of referencePeers) {
|
|
329
|
+
references.push(buildReference(
|
|
330
|
+
capability,
|
|
331
|
+
`peer identity ${capability.peerIdentity} differs from target ${hypothesis.targetIdentity}; use as a reference example only`
|
|
332
|
+
));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (createCapabilities.length === 0) {
|
|
336
|
+
warnings.push(warning(
|
|
337
|
+
'MISSING_DIRECT_NEW_FILE_SCOPE',
|
|
338
|
+
'Intent appears to add a new peer provider/proxy artifact, but direct writable scope does not include a narrow create pattern.'
|
|
339
|
+
));
|
|
340
|
+
}
|
|
341
|
+
for (const artifact of hypothesis.createRequires) {
|
|
342
|
+
if (!createCapabilities.some((item) => artifactMatches(item, artifact))) {
|
|
343
|
+
warnings.push(warning(
|
|
344
|
+
'MISSING_ARTIFACT_FAMILY_SCOPE',
|
|
345
|
+
`Intent requires provider/proxy artifact ${artifact}, but no direct create capability covers it.`,
|
|
346
|
+
{ artifact }
|
|
347
|
+
));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
for (const artifact of hypothesis.hooks) {
|
|
351
|
+
if (!genericExistingCapabilities.some((item) => artifactMatches(item, artifact))) {
|
|
352
|
+
warnings.push(warning(
|
|
353
|
+
'MISSING_REQUIRED_HOOK_SCOPE',
|
|
354
|
+
`Intent requires provider/proxy hook ${artifact}, but no direct existing-file capability covers it.`,
|
|
355
|
+
{ artifact }
|
|
356
|
+
));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (referencePeers.length > 0 && createCapabilities.length === 0) {
|
|
360
|
+
warnings.push(warning(
|
|
361
|
+
'DIRECT_ONLY_REFERENCE_PEERS',
|
|
362
|
+
'Direct writable scope only contains existing peer provider/proxy files for other identities; those are reference examples, not the target write files.'
|
|
363
|
+
));
|
|
364
|
+
}
|
|
365
|
+
if (createCapabilities.length === 0 && (scope.writableBroadFallback || []).length > 0) {
|
|
366
|
+
warnings.push(warning(
|
|
367
|
+
'BROAD_FALLBACK_ONLY',
|
|
368
|
+
'Only broad fallback scope appears able to cover new provider/proxy files; broad fallback is review-gated and is not direct write permission.'
|
|
369
|
+
));
|
|
370
|
+
}
|
|
371
|
+
} else if (
|
|
372
|
+
hypothesis.artifactFamily === 'provider_proxy'
|
|
373
|
+
&& hypothesis.targetIdentity
|
|
374
|
+
&& !hypothesis.targetAll
|
|
375
|
+
) {
|
|
376
|
+
const targetMatches = capabilities.filter((item) => (
|
|
377
|
+
item.peerIdentity && identityMatches(item.peerIdentity, hypothesis.targetIdentity)
|
|
378
|
+
));
|
|
379
|
+
const genericCapabilities = capabilities.filter((item) => (
|
|
380
|
+
!item.peerIdentity && !item.operations.includes('create_file')
|
|
381
|
+
));
|
|
382
|
+
const referencePeers = capabilities.filter((item) => (
|
|
383
|
+
item.peerIdentity && !identityMatches(item.peerIdentity, hypothesis.targetIdentity)
|
|
384
|
+
));
|
|
385
|
+
selected = unique([...targetMatches, ...genericCapabilities].map((item) => item.scope));
|
|
386
|
+
for (const capability of referencePeers) {
|
|
387
|
+
references.push(buildReference(
|
|
388
|
+
capability,
|
|
389
|
+
`peer identity ${capability.peerIdentity} differs from target ${hypothesis.targetIdentity}`
|
|
390
|
+
));
|
|
391
|
+
}
|
|
392
|
+
if (targetMatches.length === 0 && referencePeers.length > 0) {
|
|
393
|
+
warnings.push(warning(
|
|
394
|
+
'TARGET_ENTITY_MISMATCH',
|
|
395
|
+
`Intent targets ${hypothesis.targetIdentity}, but direct writable peer files are for other identities.`
|
|
396
|
+
));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
selected = unique(selected);
|
|
401
|
+
return {
|
|
402
|
+
hypotheses: [hypothesis],
|
|
403
|
+
capabilities,
|
|
404
|
+
selectedWritableDirect: selected,
|
|
405
|
+
referenceFiles: references,
|
|
406
|
+
blockingWarnings: warnings.filter((item) => item.blocking !== false),
|
|
407
|
+
warnings,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
43
411
|
function basenameTokens(file) {
|
|
44
412
|
return tokenize(path.basename(file || '').replace(/([a-z])([A-Z])/g, '$1 $2'));
|
|
45
413
|
}
|
|
@@ -734,10 +1102,22 @@ function reviewGatedNotices(scope) {
|
|
|
734
1102
|
return notices;
|
|
735
1103
|
}
|
|
736
1104
|
|
|
737
|
-
function
|
|
1105
|
+
function hasBlockingMapGapWarning(warnings = []) {
|
|
1106
|
+
return warnings.some((warning) => warning.blocking === true || [
|
|
1107
|
+
'MISSING_DIRECT_NEW_FILE_SCOPE',
|
|
1108
|
+
'DIRECT_ONLY_REFERENCE_PEERS',
|
|
1109
|
+
'BROAD_FALLBACK_ONLY',
|
|
1110
|
+
'TARGET_ENTITY_MISMATCH',
|
|
1111
|
+
'MISSING_ARTIFACT_FAMILY_SCOPE',
|
|
1112
|
+
'MISSING_REQUIRED_HOOK_SCOPE',
|
|
1113
|
+
].includes(warning.code));
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
function contextMode(area, selectedWritableDirect, validationPlan, relevant, decomposition = [], warnings = []) {
|
|
738
1117
|
if (!area) return 'needs_map';
|
|
1118
|
+
if (selectedWritableDirect.length === 0) return 'discovery_context';
|
|
1119
|
+
if (hasBlockingMapGapWarning(warnings)) return 'discovery_context';
|
|
739
1120
|
if (decomposition.some((item) => item.blocking)) return 'needs_decomposition';
|
|
740
|
-
if (scope.writableDirect.length === 0) return 'discovery_context';
|
|
741
1121
|
if (relevant.length === 0 || !relevant.some((file) => file.source === 'project_map')) return 'discovery_context';
|
|
742
1122
|
if (validationPlan.length === 0) return 'discovery_context';
|
|
743
1123
|
return 'edit_context';
|
|
@@ -850,12 +1230,31 @@ function buildContextMarkdown(packet) {
|
|
|
850
1230
|
...packet.phaseDependencyStatus.missingRequiredAcceptedPhases.map((item) => `- Missing required phase: ${item}`),
|
|
851
1231
|
'',
|
|
852
1232
|
] : []),
|
|
1233
|
+
...(packet.writePlan ? [
|
|
1234
|
+
'## Write Plan',
|
|
1235
|
+
...packet.writePlan.hypotheses.map((item) => `- ${item.action} ${item.artifactFamily}: ${item.operation}${item.targetIdentity ? ` for ${item.targetIdentity}` : ''}`),
|
|
1236
|
+
...(packet.writePlan.blockingWarnings.length
|
|
1237
|
+
? packet.writePlan.blockingWarnings.map((item) => `- Blocking: ${item.code}: ${item.message}`)
|
|
1238
|
+
: ['- Coverage: direct writable scope covers the inferred write operation.']),
|
|
1239
|
+
'',
|
|
1240
|
+
] : []),
|
|
1241
|
+
...(packet.writePlan && packet.writePlan.referenceFiles.length ? [
|
|
1242
|
+
'## Reference Files',
|
|
1243
|
+
...packet.writePlan.referenceFiles.map((file) => `- ${file.path}: ${file.reason}`),
|
|
1244
|
+
'',
|
|
1245
|
+
] : []),
|
|
853
1246
|
'## Relevant Files',
|
|
854
1247
|
...packet.relevantFiles.map((file) => `- ${file.path} (${file.role || file.source}): ${file.reason}`),
|
|
855
1248
|
'',
|
|
856
1249
|
'## Writable Scope',
|
|
857
1250
|
...(packet.writableScope.length ? packet.writableScope.map((item) => `- ${item}`) : ['- (none; this is not an edit permit)']),
|
|
858
1251
|
'',
|
|
1252
|
+
'## Writable Existing Scope',
|
|
1253
|
+
...(packet.scope.writableExisting && packet.scope.writableExisting.length ? packet.scope.writableExisting.map((item) => `- ${patternDisplay(item)}`) : ['- (none configured)']),
|
|
1254
|
+
'',
|
|
1255
|
+
'## Writable Create Patterns',
|
|
1256
|
+
...(packet.scope.writableCreatePatterns && packet.scope.writableCreatePatterns.length ? packet.scope.writableCreatePatterns.map((item) => `- ${patternDisplay(item)}`) : ['- (none configured)']),
|
|
1257
|
+
'',
|
|
859
1258
|
'## Conditional Writable Scope',
|
|
860
1259
|
...(packet.scope.writableConditional.length ? packet.scope.writableConditional.map((item) => `- ${patternDisplay(item)}`) : ['- (none configured)']),
|
|
861
1260
|
'',
|
|
@@ -921,12 +1320,24 @@ function createContextPacket(root, intent, options = {}) {
|
|
|
921
1320
|
const candidateAreas = candidateAreasFromScored(scored);
|
|
922
1321
|
const scope = area ? normalizeAreaScope(root, area) : {
|
|
923
1322
|
readable: [],
|
|
1323
|
+
readableReference: [],
|
|
924
1324
|
writableDirect: [],
|
|
1325
|
+
writableDirectItems: [],
|
|
1326
|
+
writableExisting: [],
|
|
1327
|
+
writableCreatePatterns: [],
|
|
925
1328
|
writableConditional: [],
|
|
926
1329
|
writableBroadFallback: [],
|
|
927
1330
|
forbidden: [],
|
|
928
1331
|
quality: 'unknown',
|
|
929
1332
|
};
|
|
1333
|
+
const writePlan = area ? buildWritePlan(scope, intent, area) : {
|
|
1334
|
+
hypotheses: [],
|
|
1335
|
+
capabilities: [],
|
|
1336
|
+
selectedWritableDirect: [],
|
|
1337
|
+
referenceFiles: [],
|
|
1338
|
+
blockingWarnings: [],
|
|
1339
|
+
warnings: [],
|
|
1340
|
+
};
|
|
930
1341
|
const restrictGrepToReadable = area && (confidence === 'high' || findOptions.area) && scope.readable.length > 0;
|
|
931
1342
|
const grepFiles = grepHints(root, tokens, maxFiles, {
|
|
932
1343
|
allowedPatterns: restrictGrepToReadable ? scope.readable : [],
|
|
@@ -969,6 +1380,7 @@ function createContextPacket(root, intent, options = {}) {
|
|
|
969
1380
|
});
|
|
970
1381
|
} else {
|
|
971
1382
|
warnings.push(...scopeWarnings(scope));
|
|
1383
|
+
warnings.push(...writePlan.warnings);
|
|
972
1384
|
if (relevant.length === 0 || !relevant.some((file) => file.source === 'project_map')) {
|
|
973
1385
|
warnings.push({
|
|
974
1386
|
code: 'MISSING_ENTRYPOINT',
|
|
@@ -984,7 +1396,7 @@ function createContextPacket(root, intent, options = {}) {
|
|
|
984
1396
|
}
|
|
985
1397
|
const reviewGated = area ? reviewGatedNotices(scope) : [];
|
|
986
1398
|
warnings.push(...splitRoutingWarnings(originSplit, phaseStatus, originPhaseAreaId, requestedArea));
|
|
987
|
-
const
|
|
1399
|
+
const rawDecomposition = decompositionSignals({
|
|
988
1400
|
map,
|
|
989
1401
|
area,
|
|
990
1402
|
confidence,
|
|
@@ -995,8 +1407,9 @@ function createContextPacket(root, intent, options = {}) {
|
|
|
995
1407
|
validationPlan,
|
|
996
1408
|
constrainedAreaId,
|
|
997
1409
|
});
|
|
1410
|
+
const decomposition = hasBlockingMapGapWarning(warnings) ? [] : rawDecomposition;
|
|
998
1411
|
const phaseSeeds = decomposition.length > 0 ? candidatePhaseSeeds(scored, intent) : [];
|
|
999
|
-
let mode = contextMode(area,
|
|
1412
|
+
let mode = contextMode(area, writePlan.selectedWritableDirect, validationPlan, relevant, decomposition, warnings);
|
|
1000
1413
|
if (missingSplitReference(phaseStatus) && mode === 'edit_context') mode = 'discovery_context';
|
|
1001
1414
|
const actions = recommendedActions(mode, intent, id, phaseStatus);
|
|
1002
1415
|
const packet = {
|
|
@@ -1034,7 +1447,8 @@ function createContextPacket(root, intent, options = {}) {
|
|
|
1034
1447
|
phaseDependencyStatus: phaseStatus,
|
|
1035
1448
|
relevantFiles: relevant,
|
|
1036
1449
|
scope,
|
|
1037
|
-
|
|
1450
|
+
writePlan,
|
|
1451
|
+
writableScope: writePlan.selectedWritableDirect.slice(),
|
|
1038
1452
|
readableScope: scope.readable.slice(),
|
|
1039
1453
|
doNotTouch: scope.forbidden.slice(),
|
|
1040
1454
|
validationPlan,
|
|
@@ -1076,6 +1490,7 @@ function createContextPacket(root, intent, options = {}) {
|
|
|
1076
1490
|
})),
|
|
1077
1491
|
validationQuality: validationPlan.length > 0 ? 'configured' : 'missing',
|
|
1078
1492
|
warnings,
|
|
1493
|
+
writePlan,
|
|
1079
1494
|
reviewGated,
|
|
1080
1495
|
decompositionSignals: decomposition,
|
|
1081
1496
|
candidatePhaseSeeds: phaseSeeds,
|
package/src/lib/evidence.js
CHANGED
|
@@ -106,6 +106,13 @@ function evaluateEvidencePolicy(change, map) {
|
|
|
106
106
|
let changeClass = 'unknown';
|
|
107
107
|
let verdict = 'acceptable';
|
|
108
108
|
|
|
109
|
+
if (change.writePlanStatus && change.writePlanStatus.status === 'blocked') {
|
|
110
|
+
verdict = 'blocked';
|
|
111
|
+
missing.push('write_plan_clean');
|
|
112
|
+
required.push('write_plan_clean');
|
|
113
|
+
reasons.push(...(change.writePlanStatus.blockingReasons || []).map((item) => `Write plan blocked: ${item}`));
|
|
114
|
+
}
|
|
115
|
+
|
|
109
116
|
if (change.scope.status === 'violation') {
|
|
110
117
|
verdict = 'blocked';
|
|
111
118
|
missing.push('scope_clean');
|
package/src/lib/map.js
CHANGED
|
@@ -331,10 +331,14 @@ function summarizeFindContext(context) {
|
|
|
331
331
|
},
|
|
332
332
|
warnings: context.warnings || [],
|
|
333
333
|
reviewGated: context.reviewGated || (context.explain && context.explain.reviewGated) || [],
|
|
334
|
+
writePlan: context.writePlan || (context.explain && context.explain.writePlan) || null,
|
|
334
335
|
candidateAreas: context.candidateAreas || [],
|
|
335
336
|
areaScores: context.explain && context.explain.areaScores ? context.explain.areaScores : [],
|
|
336
337
|
scopeQuality: scope.quality || (context.explain ? context.explain.scopeQuality : 'unknown'),
|
|
337
338
|
writableDirect: scope.writableDirect || context.writableScope || [],
|
|
339
|
+
selectedWritableScope: context.writableScope || [],
|
|
340
|
+
writableExisting: scope.writableExisting || [],
|
|
341
|
+
writableCreatePatterns: scope.writableCreatePatterns || [],
|
|
338
342
|
writableConditional: scope.writableConditional || [],
|
|
339
343
|
writableBroadFallback: scope.writableBroadFallback || [],
|
|
340
344
|
forbidden: scope.forbidden || context.doNotTouch || [],
|
|
@@ -431,6 +435,12 @@ function repairFocusFromFind(findContext, userFocus) {
|
|
|
431
435
|
if (isGenericAreaId(area)) goals.push(`replace generic area id with a focused id such as ${suggestedArea}`);
|
|
432
436
|
if (warnings.includes('BROAD_WRITABLE_SCOPE')) goals.push('move broad writable scope to reviewed fallback');
|
|
433
437
|
if (warnings.includes('MISSING_DIRECT_WRITABLE_SCOPE')) goals.push('add narrow writable_direct files');
|
|
438
|
+
if (warnings.includes('MISSING_DIRECT_NEW_FILE_SCOPE')) goals.push('add narrow writable_direct patterns or exact paths for the new peer files this intent must create');
|
|
439
|
+
if (warnings.includes('DIRECT_ONLY_REFERENCE_PEERS')) goals.push('move peer examples out of selected direct scope and add target create capability');
|
|
440
|
+
if (warnings.includes('BROAD_FALLBACK_ONLY')) goals.push('replace broad fallback-only coverage with narrow direct create capability');
|
|
441
|
+
if (warnings.includes('TARGET_ENTITY_MISMATCH')) goals.push('add direct scope for the requested target identity or mark old peers as references');
|
|
442
|
+
if (warnings.includes('MISSING_ARTIFACT_FAMILY_SCOPE')) goals.push('add writable_create_patterns for required artifact families');
|
|
443
|
+
if (warnings.includes('MISSING_REQUIRED_HOOK_SCOPE')) goals.push('add writable_existing hook files required by the artifact family');
|
|
434
444
|
if (warnings.includes('MISSING_ENTRYPOINT')) goals.push('add durable entrypoints');
|
|
435
445
|
if (warnings.includes('MISSING_VALIDATION')) goals.push('add validation profile references');
|
|
436
446
|
if (warnings.includes('NO_CONFIDENT_AREA')) goals.push('create or refine a focused area');
|
|
@@ -464,6 +474,15 @@ function findContextPromptSection(findContext) {
|
|
|
464
474
|
'Review-gated scope from find:',
|
|
465
475
|
...listLines(findContext.reviewGated, (notice) => `- ${notice.code}: ${notice.message}`),
|
|
466
476
|
'',
|
|
477
|
+
...(findContext.writePlan ? [
|
|
478
|
+
'Write plan from find:',
|
|
479
|
+
...listLines(findContext.writePlan.hypotheses || [], (item) => `- ${item.action} ${item.artifactFamily}: ${item.operation}${item.targetIdentity ? ` for ${item.targetIdentity}` : ''}`),
|
|
480
|
+
'Write plan blocking warnings:',
|
|
481
|
+
...listLines(findContext.writePlan.blockingWarnings || [], (item) => `- ${item.code}: ${item.message}`),
|
|
482
|
+
'Reference files from write plan:',
|
|
483
|
+
...listLines(findContext.writePlan.referenceFiles || [], (file) => `- ${file.path}: ${file.reason || ''}`),
|
|
484
|
+
'',
|
|
485
|
+
] : []),
|
|
467
486
|
'Candidate/area scores:',
|
|
468
487
|
...listLines(findContext.areaScores.length ? findContext.areaScores : findContext.candidateAreas, (area) => {
|
|
469
488
|
const signals = area.signals && area.signals.length ? ` (${area.signals.slice(0, 3).join(', ')})` : '';
|
|
@@ -476,6 +495,15 @@ function findContextPromptSection(findContext) {
|
|
|
476
495
|
'Current direct writable scope:',
|
|
477
496
|
...listLines(findContext.writableDirect, patternLine),
|
|
478
497
|
'',
|
|
498
|
+
'Selected direct writable scope for this intent:',
|
|
499
|
+
...listLines(findContext.selectedWritableScope, patternLine),
|
|
500
|
+
'',
|
|
501
|
+
'Current writable existing scope:',
|
|
502
|
+
...listLines(findContext.writableExisting, patternLine),
|
|
503
|
+
'',
|
|
504
|
+
'Current writable create patterns:',
|
|
505
|
+
...listLines(findContext.writableCreatePatterns, patternLine),
|
|
506
|
+
'',
|
|
479
507
|
'Current conditional writable scope:',
|
|
480
508
|
...listLines(findContext.writableConditional, patternLine),
|
|
481
509
|
'',
|
|
@@ -541,6 +569,9 @@ function buildMapPrompt(result) {
|
|
|
541
569
|
'- Every `writable_conditional` and `writable_broad_fallback` item must set `requires_review: true`; never output `requires_review: false` for those tiers.',
|
|
542
570
|
'- Keep broad patterns like `apps/*/src/**` only under `writable_broad_fallback`, never under `writable_direct`.',
|
|
543
571
|
'- `writable_direct` should use exact existing files unless there is a very small, justified pattern.',
|
|
572
|
+
'- Prefer `writable_existing` for exact update targets and `writable_create_patterns` for new peer files when a repo has create/update distinctions.',
|
|
573
|
+
'- Put copy sources and peer examples under `readable_reference`, not under selected Direct Writable for new peer intents.',
|
|
574
|
+
'- Use `artifact_families` to declare required create artifacts and hook files for provider/adapter/endpoint families.',
|
|
544
575
|
'- Entrypoint roles must use this fixed taxonomy only: ' + roleListText() + '.',
|
|
545
576
|
'- Do not invent role names. If unsure, use `role: unknown` plus a TODO note.',
|
|
546
577
|
'- Add `source` and `confidence` to entrypoints when possible. Use `source: agent_draft` for your own draft and `confidence: low|medium|high`.',
|
|
@@ -594,8 +625,27 @@ function buildMapPrompt(result) {
|
|
|
594
625
|
' scope:',
|
|
595
626
|
' readable:',
|
|
596
627
|
' - path/to/read/**',
|
|
628
|
+
' readable_reference:',
|
|
629
|
+
' peer_examples:',
|
|
630
|
+
' - path: path/to/ExistingPeer.ts',
|
|
631
|
+
' artifact: service_impl',
|
|
632
|
+
' peer_identity: Existing',
|
|
597
633
|
' writable_direct:',
|
|
598
634
|
' - path/to/real/file.ts',
|
|
635
|
+
' writable_existing:',
|
|
636
|
+
' - path: path/to/Registry.ts',
|
|
637
|
+
' artifact: registry_hook',
|
|
638
|
+
' operations:',
|
|
639
|
+
' - update_existing',
|
|
640
|
+
' required_when:',
|
|
641
|
+
' - add_provider_proxy',
|
|
642
|
+
' writable_create_patterns:',
|
|
643
|
+
' - pattern: path/to/*Provider.ts',
|
|
644
|
+
' artifact: service_impl',
|
|
645
|
+
' operations:',
|
|
646
|
+
' - create_file',
|
|
647
|
+
' intent_kinds:',
|
|
648
|
+
' - add_provider_proxy',
|
|
599
649
|
' writable_conditional:',
|
|
600
650
|
' - pattern: path/to/shared/**',
|
|
601
651
|
' reason: only if shared model changes are required',
|
|
@@ -623,6 +673,19 @@ function buildMapPrompt(result) {
|
|
|
623
673
|
' default_level: low',
|
|
624
674
|
' tags:',
|
|
625
675
|
' - ui',
|
|
676
|
+
' artifact_families:',
|
|
677
|
+
' provider_proxy:',
|
|
678
|
+
' trigger_terms:',
|
|
679
|
+
' en:',
|
|
680
|
+
' - provider',
|
|
681
|
+
' - proxy',
|
|
682
|
+
' zh:',
|
|
683
|
+
' - 提供商',
|
|
684
|
+
' - 代理',
|
|
685
|
+
' create_requires:',
|
|
686
|
+
' - service_impl',
|
|
687
|
+
' hooks:',
|
|
688
|
+
' - registry_hook',
|
|
626
689
|
'```',
|
|
627
690
|
'',
|
|
628
691
|
'Validation profile schema example:',
|
package/src/lib/risk.js
CHANGED
|
@@ -54,6 +54,11 @@ function evaluateChangeRisk(change, map) {
|
|
|
54
54
|
if (change.scope.status === 'violation') {
|
|
55
55
|
blockingReasons.push(`scope violation: ${change.scope.violations.join(', ')}`);
|
|
56
56
|
}
|
|
57
|
+
if (change.writePlanStatus && change.writePlanStatus.status === 'blocked') {
|
|
58
|
+
for (const reason of change.writePlanStatus.blockingReasons || []) {
|
|
59
|
+
blockingReasons.push(`write plan blocked: ${reason}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
57
62
|
|
|
58
63
|
const status = validationStatus(change);
|
|
59
64
|
if (status === 'failed') blockingReasons.push('validation failed');
|
package/src/lib/scope.js
CHANGED
|
@@ -12,6 +12,12 @@ function patternFrom(value) {
|
|
|
12
12
|
return normalizePath((value.pattern || value.path || '').trim());
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function arrayFrom(value) {
|
|
16
|
+
if (Array.isArray(value)) return value.map((item) => String(item || '')).filter(Boolean);
|
|
17
|
+
if (value == null || value === '') return [];
|
|
18
|
+
return [String(value)];
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
function normalizePattern(value, defaults = {}) {
|
|
16
22
|
const pattern = patternFrom(value);
|
|
17
23
|
if (!pattern) return null;
|
|
@@ -22,6 +28,13 @@ function normalizePattern(value, defaults = {}) {
|
|
|
22
28
|
requiresReview: defaults.requiresReview === true,
|
|
23
29
|
source: defaults.source || 'map',
|
|
24
30
|
matchCount: defaults.matchCount == null ? null : defaults.matchCount,
|
|
31
|
+
artifact: defaults.artifact || null,
|
|
32
|
+
artifactFamily: defaults.artifactFamily || null,
|
|
33
|
+
operations: arrayFrom(defaults.operations),
|
|
34
|
+
targetIdentity: defaults.targetIdentity || null,
|
|
35
|
+
targetSlot: defaults.targetSlot || null,
|
|
36
|
+
intentKinds: arrayFrom(defaults.intentKinds),
|
|
37
|
+
requiredWhen: arrayFrom(defaults.requiredWhen),
|
|
25
38
|
};
|
|
26
39
|
}
|
|
27
40
|
return {
|
|
@@ -30,6 +43,13 @@ function normalizePattern(value, defaults = {}) {
|
|
|
30
43
|
requiresReview: value.requires_review === true || value.requiresReview === true || defaults.requiresReview === true,
|
|
31
44
|
source: value.source || defaults.source || 'map',
|
|
32
45
|
matchCount: value.matchCount == null ? (defaults.matchCount == null ? null : defaults.matchCount) : value.matchCount,
|
|
46
|
+
artifact: value.artifact || defaults.artifact || null,
|
|
47
|
+
artifactFamily: value.artifact_family || value.artifactFamily || defaults.artifactFamily || null,
|
|
48
|
+
operations: arrayFrom(value.operations || value.operation || defaults.operations),
|
|
49
|
+
targetIdentity: value.target_identity || value.targetIdentity || value.peer_identity || value.peerIdentity || defaults.targetIdentity || null,
|
|
50
|
+
targetSlot: value.target_slot || value.targetSlot || defaults.targetSlot || null,
|
|
51
|
+
intentKinds: arrayFrom(value.intent_kinds || value.intentKinds || defaults.intentKinds),
|
|
52
|
+
requiredWhen: arrayFrom(value.required_when || value.requiredWhen || defaults.requiredWhen),
|
|
33
53
|
};
|
|
34
54
|
}
|
|
35
55
|
|
|
@@ -93,29 +113,119 @@ function splitDirectAndBroad(root, values, defaults = {}) {
|
|
|
93
113
|
return { direct, broad };
|
|
94
114
|
}
|
|
95
115
|
|
|
116
|
+
function splitItemsAndBroad(root, values, defaults = {}) {
|
|
117
|
+
const direct = [];
|
|
118
|
+
const broad = [];
|
|
119
|
+
for (const value of values || []) {
|
|
120
|
+
const pattern = patternFrom(value);
|
|
121
|
+
if (!pattern) continue;
|
|
122
|
+
const classification = isBroadPattern(root, pattern);
|
|
123
|
+
const item = normalizePattern(value, {
|
|
124
|
+
...defaults,
|
|
125
|
+
matchCount: classification.matchCount,
|
|
126
|
+
});
|
|
127
|
+
if (!item) continue;
|
|
128
|
+
if (classification.broad) {
|
|
129
|
+
broad.push({
|
|
130
|
+
...item,
|
|
131
|
+
reason: item.reason || classification.reason || 'broad writable scope',
|
|
132
|
+
requiresReview: true,
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
direct.push(item);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { direct, broad };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function flattenReferenceValues(value) {
|
|
142
|
+
if (!value) return [];
|
|
143
|
+
if (Array.isArray(value)) return value;
|
|
144
|
+
if (typeof value === 'object' && (value.path || value.pattern)) return [value];
|
|
145
|
+
if (typeof value === 'object') {
|
|
146
|
+
return Object.values(value).flatMap((item) => flattenReferenceValues(item));
|
|
147
|
+
}
|
|
148
|
+
return [value];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeReference(value, defaults = {}) {
|
|
152
|
+
const pattern = patternFrom(value);
|
|
153
|
+
if (!pattern) return null;
|
|
154
|
+
if (typeof value === 'string') {
|
|
155
|
+
return {
|
|
156
|
+
path: pattern,
|
|
157
|
+
reason: defaults.reason || '',
|
|
158
|
+
role: defaults.role || 'reference',
|
|
159
|
+
artifact: defaults.artifact || null,
|
|
160
|
+
artifactFamily: defaults.artifactFamily || null,
|
|
161
|
+
peerIdentity: defaults.peerIdentity || null,
|
|
162
|
+
source: defaults.source || 'readable_reference',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
path: pattern,
|
|
167
|
+
reason: value.reason || defaults.reason || '',
|
|
168
|
+
role: value.role || defaults.role || 'reference',
|
|
169
|
+
artifact: value.artifact || defaults.artifact || null,
|
|
170
|
+
artifactFamily: value.artifact_family || value.artifactFamily || defaults.artifactFamily || null,
|
|
171
|
+
peerIdentity: value.peer_identity || value.peerIdentity || value.provider || defaults.peerIdentity || null,
|
|
172
|
+
source: value.source || defaults.source || 'readable_reference',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function normalizeReferenceList(value, defaults = {}) {
|
|
177
|
+
return flattenReferenceValues(value).map((item) => normalizeReference(item, defaults)).filter(Boolean);
|
|
178
|
+
}
|
|
179
|
+
|
|
96
180
|
function normalizeAreaScope(root, area = {}) {
|
|
97
181
|
const configured = area.scope || {};
|
|
98
182
|
const readable = (configured.readable || area.readable_scope || []).map(patternFrom).filter(Boolean);
|
|
183
|
+
const readableReference = normalizeReferenceList(configured.readable_reference || area.readable_reference || [], {
|
|
184
|
+
source: 'readable_reference',
|
|
185
|
+
});
|
|
99
186
|
const forbidden = (configured.forbidden || area.do_not_touch || []).map(patternFrom).filter(Boolean);
|
|
100
187
|
|
|
101
188
|
const directValues = configured.writable_direct || [];
|
|
189
|
+
const existingValues = configured.writable_existing || [];
|
|
190
|
+
const createValues = configured.writable_create_patterns || [];
|
|
102
191
|
const conditionalValues = configured.writable_conditional || [];
|
|
103
192
|
const broadValues = configured.writable_broad_fallback || [];
|
|
104
193
|
const legacyWritable = !area.scope ? (area.writable_scope || []) : [];
|
|
105
194
|
|
|
106
|
-
const directSplit =
|
|
195
|
+
const directSplit = splitItemsAndBroad(root, directValues, { source: 'writable_direct' });
|
|
196
|
+
const existingSplit = splitItemsAndBroad(root, existingValues, {
|
|
197
|
+
source: 'writable_existing',
|
|
198
|
+
operations: ['update_existing'],
|
|
199
|
+
});
|
|
200
|
+
const createSplit = splitItemsAndBroad(root, createValues, {
|
|
201
|
+
source: 'writable_create_patterns',
|
|
202
|
+
operations: ['create_file'],
|
|
203
|
+
});
|
|
107
204
|
const legacySplit = splitDirectAndBroad(root, legacyWritable, {
|
|
108
205
|
source: 'legacy_writable_scope',
|
|
109
206
|
reason: 'legacy writable_scope is broad; move this to scope.writable_broad_fallback or add writable_direct',
|
|
110
207
|
});
|
|
111
208
|
|
|
112
|
-
const
|
|
209
|
+
const legacyDirectItems = legacySplit.direct.map((pattern) => normalizePattern(pattern, {
|
|
210
|
+
source: 'legacy_writable_scope',
|
|
211
|
+
})).filter(Boolean);
|
|
212
|
+
const writableDirectItems = [
|
|
213
|
+
...directSplit.direct,
|
|
214
|
+
...existingSplit.direct,
|
|
215
|
+
...createSplit.direct,
|
|
216
|
+
...legacyDirectItems,
|
|
217
|
+
];
|
|
218
|
+
const writableDirect = writableDirectItems.map((item) => item.pattern);
|
|
219
|
+
const writableExisting = existingSplit.direct;
|
|
220
|
+
const writableCreatePatterns = createSplit.direct;
|
|
113
221
|
const writableConditional = normalizePatternList(conditionalValues, {
|
|
114
222
|
source: 'writable_conditional',
|
|
115
223
|
requiresReview: true,
|
|
116
224
|
});
|
|
117
225
|
const writableBroadFallback = [
|
|
118
226
|
...directSplit.broad,
|
|
227
|
+
...existingSplit.broad,
|
|
228
|
+
...createSplit.broad,
|
|
119
229
|
...normalizePatternList(broadValues, {
|
|
120
230
|
source: 'writable_broad_fallback',
|
|
121
231
|
requiresReview: true,
|
|
@@ -139,7 +249,11 @@ function normalizeAreaScope(root, area = {}) {
|
|
|
139
249
|
|
|
140
250
|
return {
|
|
141
251
|
readable,
|
|
252
|
+
readableReference,
|
|
142
253
|
writableDirect,
|
|
254
|
+
writableDirectItems,
|
|
255
|
+
writableExisting,
|
|
256
|
+
writableCreatePatterns,
|
|
143
257
|
writableConditional,
|
|
144
258
|
writableBroadFallback,
|
|
145
259
|
forbidden,
|
|
@@ -164,5 +278,6 @@ module.exports = {
|
|
|
164
278
|
itemPattern,
|
|
165
279
|
normalizeAreaScope,
|
|
166
280
|
normalizePatternList,
|
|
281
|
+
normalizeReferenceList,
|
|
167
282
|
patternDisplay,
|
|
168
283
|
};
|