cclaw-cli 7.7.1 → 8.1.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.
- package/README.md +211 -134
- package/dist/artifact-frontmatter.d.ts +51 -0
- package/dist/artifact-frontmatter.js +131 -0
- package/dist/artifact-paths.d.ts +7 -27
- package/dist/artifact-paths.js +20 -249
- package/dist/cancel.d.ts +16 -0
- package/dist/cancel.js +66 -0
- package/dist/cli.d.ts +2 -27
- package/dist/cli.js +107 -511
- package/dist/compound.d.ts +26 -0
- package/dist/compound.js +96 -0
- package/dist/config.d.ts +14 -51
- package/dist/config.js +23 -359
- package/dist/constants.d.ts +11 -18
- package/dist/constants.js +19 -106
- package/dist/content/antipatterns.d.ts +1 -0
- package/dist/content/antipatterns.js +109 -0
- package/dist/content/artifact-templates.d.ts +10 -0
- package/dist/content/artifact-templates.js +550 -0
- package/dist/content/cancel-command.d.ts +2 -2
- package/dist/content/cancel-command.js +25 -17
- package/dist/content/core-agents.d.ts +9 -233
- package/dist/content/core-agents.js +39 -768
- package/dist/content/decision-protocol.d.ts +1 -12
- package/dist/content/decision-protocol.js +27 -20
- package/dist/content/examples.d.ts +8 -42
- package/dist/content/examples.js +293 -425
- package/dist/content/idea-command.d.ts +2 -0
- package/dist/content/idea-command.js +38 -0
- package/dist/content/iron-laws.d.ts +4 -138
- package/dist/content/iron-laws.js +18 -197
- package/dist/content/meta-skill.d.ts +1 -3
- package/dist/content/meta-skill.js +57 -134
- package/dist/content/node-hooks.d.ts +12 -8
- package/dist/content/node-hooks.js +188 -838
- package/dist/content/recovery.d.ts +8 -0
- package/dist/content/recovery.js +179 -0
- package/dist/content/reference-patterns.d.ts +4 -13
- package/dist/content/reference-patterns.js +260 -389
- package/dist/content/research-playbooks.d.ts +8 -8
- package/dist/content/research-playbooks.js +108 -121
- package/dist/content/review-loop.d.ts +6 -192
- package/dist/content/review-loop.js +29 -731
- package/dist/content/skills.d.ts +8 -38
- package/dist/content/skills.js +681 -732
- package/dist/content/specialist-prompts/architect.d.ts +1 -0
- package/dist/content/specialist-prompts/architect.js +225 -0
- package/dist/content/specialist-prompts/brainstormer.d.ts +1 -0
- package/dist/content/specialist-prompts/brainstormer.js +168 -0
- package/dist/content/specialist-prompts/index.d.ts +2 -0
- package/dist/content/specialist-prompts/index.js +14 -0
- package/dist/content/specialist-prompts/planner.d.ts +1 -0
- package/dist/content/specialist-prompts/planner.js +182 -0
- package/dist/content/specialist-prompts/reviewer.d.ts +1 -0
- package/dist/content/specialist-prompts/reviewer.js +193 -0
- package/dist/content/specialist-prompts/security-reviewer.d.ts +1 -0
- package/dist/content/specialist-prompts/security-reviewer.js +133 -0
- package/dist/content/specialist-prompts/slice-builder.d.ts +1 -0
- package/dist/content/specialist-prompts/slice-builder.js +232 -0
- package/dist/content/stage-playbooks.d.ts +8 -0
- package/dist/content/stage-playbooks.js +404 -0
- package/dist/content/start-command.d.ts +2 -12
- package/dist/content/start-command.js +221 -207
- package/dist/flow-state.d.ts +21 -178
- package/dist/flow-state.js +67 -170
- package/dist/fs-utils.d.ts +6 -26
- package/dist/fs-utils.js +29 -162
- package/dist/gitignore.d.ts +2 -1
- package/dist/gitignore.js +51 -34
- package/dist/harness-detect.d.ts +10 -0
- package/dist/harness-detect.js +29 -0
- package/dist/harness-prompt.d.ts +26 -0
- package/dist/harness-prompt.js +142 -0
- package/dist/install.d.ts +35 -15
- package/dist/install.js +238 -1347
- package/dist/knowledge-store.d.ts +19 -163
- package/dist/knowledge-store.js +56 -590
- package/dist/logger.d.ts +8 -3
- package/dist/logger.js +13 -4
- package/dist/orchestrator-routing.d.ts +29 -0
- package/dist/orchestrator-routing.js +156 -0
- package/dist/run-persistence.d.ts +7 -118
- package/dist/run-persistence.js +29 -845
- package/dist/runtime/run-hook.entry.d.ts +1 -3
- package/dist/runtime/run-hook.entry.js +19 -4
- package/dist/runtime/run-hook.mjs +13 -1024
- package/dist/types.d.ts +25 -261
- package/dist/types.js +8 -36
- package/package.json +6 -3
- package/dist/artifact-linter/brainstorm.d.ts +0 -2
- package/dist/artifact-linter/brainstorm.js +0 -353
- package/dist/artifact-linter/design.d.ts +0 -18
- package/dist/artifact-linter/design.js +0 -444
- package/dist/artifact-linter/findings-dedup.d.ts +0 -56
- package/dist/artifact-linter/findings-dedup.js +0 -232
- package/dist/artifact-linter/plan.d.ts +0 -2
- package/dist/artifact-linter/plan.js +0 -826
- package/dist/artifact-linter/review-army.d.ts +0 -49
- package/dist/artifact-linter/review-army.js +0 -520
- package/dist/artifact-linter/review.d.ts +0 -2
- package/dist/artifact-linter/review.js +0 -113
- package/dist/artifact-linter/scope.d.ts +0 -2
- package/dist/artifact-linter/scope.js +0 -158
- package/dist/artifact-linter/shared.d.ts +0 -637
- package/dist/artifact-linter/shared.js +0 -2163
- package/dist/artifact-linter/ship.d.ts +0 -2
- package/dist/artifact-linter/ship.js +0 -250
- package/dist/artifact-linter/spec.d.ts +0 -2
- package/dist/artifact-linter/spec.js +0 -176
- package/dist/artifact-linter/tdd.d.ts +0 -118
- package/dist/artifact-linter/tdd.js +0 -1404
- package/dist/artifact-linter.d.ts +0 -15
- package/dist/artifact-linter.js +0 -517
- package/dist/codex-feature-flag.d.ts +0 -58
- package/dist/codex-feature-flag.js +0 -193
- package/dist/content/closeout-guidance.d.ts +0 -14
- package/dist/content/closeout-guidance.js +0 -44
- package/dist/content/diff-command.d.ts +0 -1
- package/dist/content/diff-command.js +0 -43
- package/dist/content/harness-doc.d.ts +0 -1
- package/dist/content/harness-doc.js +0 -65
- package/dist/content/hook-events.d.ts +0 -9
- package/dist/content/hook-events.js +0 -23
- package/dist/content/hook-manifest.d.ts +0 -81
- package/dist/content/hook-manifest.js +0 -156
- package/dist/content/hooks.d.ts +0 -11
- package/dist/content/hooks.js +0 -1972
- package/dist/content/idea.d.ts +0 -60
- package/dist/content/idea.js +0 -416
- package/dist/content/language-policy.d.ts +0 -2
- package/dist/content/language-policy.js +0 -13
- package/dist/content/learnings.d.ts +0 -6
- package/dist/content/learnings.js +0 -141
- package/dist/content/observe.d.ts +0 -19
- package/dist/content/observe.js +0 -86
- package/dist/content/opencode-plugin.d.ts +0 -1
- package/dist/content/opencode-plugin.js +0 -635
- package/dist/content/review-prompts.d.ts +0 -1
- package/dist/content/review-prompts.js +0 -104
- package/dist/content/runtime-shared-snippets.d.ts +0 -8
- package/dist/content/runtime-shared-snippets.js +0 -80
- package/dist/content/session-hooks.d.ts +0 -7
- package/dist/content/session-hooks.js +0 -107
- package/dist/content/skills-elicitation.d.ts +0 -1
- package/dist/content/skills-elicitation.js +0 -167
- package/dist/content/stage-command.d.ts +0 -2
- package/dist/content/stage-command.js +0 -17
- package/dist/content/stage-schema.d.ts +0 -117
- package/dist/content/stage-schema.js +0 -955
- package/dist/content/stages/_lint-metadata/index.d.ts +0 -2
- package/dist/content/stages/_lint-metadata/index.js +0 -97
- package/dist/content/stages/brainstorm.d.ts +0 -2
- package/dist/content/stages/brainstorm.js +0 -184
- package/dist/content/stages/design.d.ts +0 -2
- package/dist/content/stages/design.js +0 -288
- package/dist/content/stages/index.d.ts +0 -8
- package/dist/content/stages/index.js +0 -11
- package/dist/content/stages/plan.d.ts +0 -2
- package/dist/content/stages/plan.js +0 -191
- package/dist/content/stages/review.d.ts +0 -2
- package/dist/content/stages/review.js +0 -240
- package/dist/content/stages/schema-types.d.ts +0 -203
- package/dist/content/stages/schema-types.js +0 -1
- package/dist/content/stages/scope.d.ts +0 -2
- package/dist/content/stages/scope.js +0 -254
- package/dist/content/stages/ship.d.ts +0 -2
- package/dist/content/stages/ship.js +0 -159
- package/dist/content/stages/spec.d.ts +0 -2
- package/dist/content/stages/spec.js +0 -170
- package/dist/content/stages/tdd.d.ts +0 -4
- package/dist/content/stages/tdd.js +0 -273
- package/dist/content/state-contracts.d.ts +0 -1
- package/dist/content/state-contracts.js +0 -63
- package/dist/content/status-command.d.ts +0 -4
- package/dist/content/status-command.js +0 -109
- package/dist/content/subagent-context-skills.d.ts +0 -4
- package/dist/content/subagent-context-skills.js +0 -279
- package/dist/content/subagents.d.ts +0 -3
- package/dist/content/subagents.js +0 -997
- package/dist/content/templates.d.ts +0 -26
- package/dist/content/templates.js +0 -1692
- package/dist/content/track-render-context.d.ts +0 -18
- package/dist/content/track-render-context.js +0 -53
- package/dist/content/tree-command.d.ts +0 -1
- package/dist/content/tree-command.js +0 -64
- package/dist/content/utility-skills.d.ts +0 -30
- package/dist/content/utility-skills.js +0 -160
- package/dist/content/view-command.d.ts +0 -2
- package/dist/content/view-command.js +0 -92
- package/dist/delegation.d.ts +0 -649
- package/dist/delegation.js +0 -1539
- package/dist/early-loop.d.ts +0 -70
- package/dist/early-loop.js +0 -302
- package/dist/execution-topology.d.ts +0 -44
- package/dist/execution-topology.js +0 -95
- package/dist/gate-evidence.d.ts +0 -85
- package/dist/gate-evidence.js +0 -631
- package/dist/harness-adapters.d.ts +0 -151
- package/dist/harness-adapters.js +0 -756
- package/dist/harness-selection.d.ts +0 -31
- package/dist/harness-selection.js +0 -214
- package/dist/hook-schema.d.ts +0 -6
- package/dist/hook-schema.js +0 -114
- package/dist/hook-schemas/claude-hooks.v1.json +0 -10
- package/dist/hook-schemas/codex-hooks.v1.json +0 -10
- package/dist/hook-schemas/cursor-hooks.v1.json +0 -13
- package/dist/init-detect.d.ts +0 -2
- package/dist/init-detect.js +0 -50
- package/dist/internal/advance-stage/advance.d.ts +0 -89
- package/dist/internal/advance-stage/advance.js +0 -655
- package/dist/internal/advance-stage/cancel-run.d.ts +0 -8
- package/dist/internal/advance-stage/cancel-run.js +0 -19
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +0 -3
- package/dist/internal/advance-stage/flow-state-coercion.js +0 -81
- package/dist/internal/advance-stage/helpers.d.ts +0 -14
- package/dist/internal/advance-stage/helpers.js +0 -145
- package/dist/internal/advance-stage/hook.d.ts +0 -8
- package/dist/internal/advance-stage/hook.js +0 -40
- package/dist/internal/advance-stage/parsers.d.ts +0 -72
- package/dist/internal/advance-stage/parsers.js +0 -357
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +0 -24
- package/dist/internal/advance-stage/proactive-delegation-trace.js +0 -56
- package/dist/internal/advance-stage/review-loop.d.ts +0 -16
- package/dist/internal/advance-stage/review-loop.js +0 -199
- package/dist/internal/advance-stage/rewind.d.ts +0 -14
- package/dist/internal/advance-stage/rewind.js +0 -108
- package/dist/internal/advance-stage/start-flow.d.ts +0 -13
- package/dist/internal/advance-stage/start-flow.js +0 -241
- package/dist/internal/advance-stage/verify.d.ts +0 -21
- package/dist/internal/advance-stage/verify.js +0 -185
- package/dist/internal/advance-stage.d.ts +0 -7
- package/dist/internal/advance-stage.js +0 -138
- package/dist/internal/cohesion-contract-stub.d.ts +0 -24
- package/dist/internal/cohesion-contract-stub.js +0 -148
- package/dist/internal/compound-readiness.d.ts +0 -23
- package/dist/internal/compound-readiness.js +0 -102
- package/dist/internal/detect-public-api-changes.d.ts +0 -5
- package/dist/internal/detect-public-api-changes.js +0 -45
- package/dist/internal/detect-supply-chain-changes.d.ts +0 -6
- package/dist/internal/detect-supply-chain-changes.js +0 -138
- package/dist/internal/early-loop-status.d.ts +0 -7
- package/dist/internal/early-loop-status.js +0 -93
- package/dist/internal/envelope-validate.d.ts +0 -7
- package/dist/internal/envelope-validate.js +0 -66
- package/dist/internal/flow-state-repair.d.ts +0 -20
- package/dist/internal/flow-state-repair.js +0 -104
- package/dist/internal/plan-split-waves.d.ts +0 -190
- package/dist/internal/plan-split-waves.js +0 -764
- package/dist/internal/runtime-integrity.d.ts +0 -7
- package/dist/internal/runtime-integrity.js +0 -268
- package/dist/internal/slice-commit.d.ts +0 -7
- package/dist/internal/slice-commit.js +0 -619
- package/dist/internal/tdd-loop-status.d.ts +0 -14
- package/dist/internal/tdd-loop-status.js +0 -68
- package/dist/internal/tdd-red-evidence.d.ts +0 -7
- package/dist/internal/tdd-red-evidence.js +0 -153
- package/dist/internal/waiver-grant.d.ts +0 -62
- package/dist/internal/waiver-grant.js +0 -294
- package/dist/internal/wave-status.d.ts +0 -74
- package/dist/internal/wave-status.js +0 -506
- package/dist/managed-resources.d.ts +0 -53
- package/dist/managed-resources.js +0 -313
- package/dist/policy.d.ts +0 -10
- package/dist/policy.js +0 -167
- package/dist/retro-gate.d.ts +0 -9
- package/dist/retro-gate.js +0 -47
- package/dist/run-archive.d.ts +0 -61
- package/dist/run-archive.js +0 -391
- package/dist/runs.d.ts +0 -2
- package/dist/runs.js +0 -2
- package/dist/stack-detection.d.ts +0 -116
- package/dist/stack-detection.js +0 -489
- package/dist/streaming/event-stream.d.ts +0 -31
- package/dist/streaming/event-stream.js +0 -114
- package/dist/tdd-cycle.d.ts +0 -107
- package/dist/tdd-cycle.js +0 -289
- package/dist/tdd-verification-evidence.d.ts +0 -17
- package/dist/tdd-verification-evidence.js +0 -122
- package/dist/track-heuristics.d.ts +0 -27
- package/dist/track-heuristics.js +0 -154
- package/dist/util/slice-id.d.ts +0 -58
- package/dist/util/slice-id.js +0 -89
- package/dist/worktree-manager.d.ts +0 -20
- package/dist/worktree-manager.js +0 -108
package/dist/knowledge-store.js
CHANGED
|
@@ -1,603 +1,69 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { FLOW_STAGES } from "./types.js";
|
|
7
|
-
const DEFAULT_COMPOUND_READINESS_MAX_READY = 10;
|
|
8
|
-
/**
|
|
9
|
-
* Single source of truth for the small-project relaxation rule.
|
|
10
|
-
*
|
|
11
|
-
* Kept exported so the inline hook mirror and CLI/runtime paths all agree on
|
|
12
|
-
* the same numbers.
|
|
13
|
-
*/
|
|
14
|
-
export const SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD = 5;
|
|
15
|
-
export const SMALL_PROJECT_RECURRENCE_THRESHOLD = 2;
|
|
16
|
-
export function effectiveCompoundThreshold(baseThreshold, archivedRunsCount) {
|
|
17
|
-
if (typeof archivedRunsCount === "number" &&
|
|
18
|
-
Number.isFinite(archivedRunsCount) &&
|
|
19
|
-
archivedRunsCount < SMALL_PROJECT_ARCHIVE_RUNS_THRESHOLD &&
|
|
20
|
-
baseThreshold > SMALL_PROJECT_RECURRENCE_THRESHOLD) {
|
|
21
|
-
return {
|
|
22
|
-
threshold: SMALL_PROJECT_RECURRENCE_THRESHOLD,
|
|
23
|
-
relaxationApplied: true
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
return { threshold: baseThreshold, relaxationApplied: false };
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Pure function — no filesystem side effects. Callers pass entries from
|
|
30
|
-
* `readKnowledgeSafely` and get a derived readiness snapshot suitable
|
|
31
|
-
* for persisting to `.cclaw/state/compound-readiness.json`.
|
|
32
|
-
*
|
|
33
|
-
* Clustering key: `(type, normalizeText(trigger), normalizeText(action))`
|
|
34
|
-
* which mirrors the compound readiness clustering in runtime state.
|
|
35
|
-
* The readiness surface intentionally stays simple: no maturity/supersede
|
|
36
|
-
* suppression logic in this pass.
|
|
37
|
-
*/
|
|
38
|
-
export function computeCompoundReadiness(entries, options = {}) {
|
|
39
|
-
const thresholdRaw = options.threshold ?? DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
|
|
40
|
-
const baseThreshold = Number.isInteger(thresholdRaw) && thresholdRaw >= 1
|
|
41
|
-
? thresholdRaw
|
|
42
|
-
: DEFAULT_COMPOUND_RECURRENCE_THRESHOLD;
|
|
43
|
-
const maxReadyRaw = options.maxReady ?? DEFAULT_COMPOUND_READINESS_MAX_READY;
|
|
44
|
-
const maxReady = Number.isInteger(maxReadyRaw) && maxReadyRaw >= 1
|
|
45
|
-
? maxReadyRaw
|
|
46
|
-
: DEFAULT_COMPOUND_READINESS_MAX_READY;
|
|
47
|
-
const now = options.now ?? new Date();
|
|
48
|
-
const archivedRunsCount = typeof options.archivedRunsCount === "number" &&
|
|
49
|
-
Number.isFinite(options.archivedRunsCount) &&
|
|
50
|
-
options.archivedRunsCount >= 0
|
|
51
|
-
? Math.floor(options.archivedRunsCount)
|
|
52
|
-
: undefined;
|
|
53
|
-
const { threshold, relaxationApplied } = effectiveCompoundThreshold(baseThreshold, archivedRunsCount);
|
|
54
|
-
const buckets = new Map();
|
|
55
|
-
for (const entry of entries) {
|
|
56
|
-
const key = [
|
|
57
|
-
entry.type,
|
|
58
|
-
normalizeText(entry.trigger),
|
|
59
|
-
normalizeText(entry.action)
|
|
60
|
-
].join("||");
|
|
61
|
-
const frequency = Math.max(1, Math.floor(entry.frequency));
|
|
62
|
-
const bucket = buckets.get(key);
|
|
63
|
-
if (!bucket) {
|
|
64
|
-
buckets.set(key, {
|
|
65
|
-
trigger: entry.trigger,
|
|
66
|
-
action: entry.action,
|
|
67
|
-
recurrence: frequency,
|
|
68
|
-
entryCount: 1,
|
|
69
|
-
severity: entry.severity,
|
|
70
|
-
lastSeenTs: entry.last_seen_ts,
|
|
71
|
-
types: new Set([entry.type])
|
|
72
|
-
});
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
bucket.recurrence += frequency;
|
|
76
|
-
bucket.entryCount += 1;
|
|
77
|
-
bucket.types.add(entry.type);
|
|
78
|
-
if (entry.severity === "critical") {
|
|
79
|
-
bucket.severity = "critical";
|
|
80
|
-
}
|
|
81
|
-
else if (entry.severity === "important" && bucket.severity !== "critical") {
|
|
82
|
-
bucket.severity = "important";
|
|
83
|
-
}
|
|
84
|
-
if (Date.parse(entry.last_seen_ts) > Date.parse(bucket.lastSeenTs)) {
|
|
85
|
-
bucket.lastSeenTs = entry.last_seen_ts;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
const ready = [];
|
|
89
|
-
for (const bucket of buckets.values()) {
|
|
90
|
-
const criticalOverride = bucket.severity === "critical";
|
|
91
|
-
const meetsRecurrence = bucket.recurrence >= threshold;
|
|
92
|
-
if (!criticalOverride && !meetsRecurrence)
|
|
93
|
-
continue;
|
|
94
|
-
ready.push({
|
|
95
|
-
trigger: bucket.trigger,
|
|
96
|
-
action: bucket.action,
|
|
97
|
-
recurrence: bucket.recurrence,
|
|
98
|
-
entryCount: bucket.entryCount,
|
|
99
|
-
qualification: criticalOverride && !meetsRecurrence ? "critical_override" : "recurrence",
|
|
100
|
-
...(bucket.severity ? { severity: bucket.severity } : {}),
|
|
101
|
-
lastSeenTs: bucket.lastSeenTs,
|
|
102
|
-
types: Array.from(bucket.types).sort()
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
ready.sort((a, b) => {
|
|
106
|
-
const severityWeight = (sev) => {
|
|
107
|
-
if (sev === "critical")
|
|
108
|
-
return 3;
|
|
109
|
-
if (sev === "important")
|
|
110
|
-
return 2;
|
|
111
|
-
if (sev === "suggestion")
|
|
112
|
-
return 1;
|
|
113
|
-
return 0;
|
|
114
|
-
};
|
|
115
|
-
const severityDiff = severityWeight(b.severity) - severityWeight(a.severity);
|
|
116
|
-
if (severityDiff !== 0)
|
|
117
|
-
return severityDiff;
|
|
118
|
-
if (b.recurrence !== a.recurrence)
|
|
119
|
-
return b.recurrence - a.recurrence;
|
|
120
|
-
const recencyDiff = Date.parse(b.lastSeenTs) - Date.parse(a.lastSeenTs);
|
|
121
|
-
if (!Number.isNaN(recencyDiff) && recencyDiff !== 0)
|
|
122
|
-
return recencyDiff;
|
|
123
|
-
return a.trigger.localeCompare(b.trigger);
|
|
124
|
-
});
|
|
125
|
-
return {
|
|
126
|
-
schemaVersion: 2,
|
|
127
|
-
threshold,
|
|
128
|
-
baseThreshold,
|
|
129
|
-
...(archivedRunsCount !== undefined ? { archivedRunsCount } : {}),
|
|
130
|
-
smallProjectRelaxationApplied: relaxationApplied,
|
|
131
|
-
clusterCount: buckets.size,
|
|
132
|
-
readyCount: ready.length,
|
|
133
|
-
ready: ready.slice(0, maxReady),
|
|
134
|
-
lastUpdatedAt: normalizeUtcIso(now.toISOString())
|
|
135
|
-
};
|
|
3
|
+
import { KNOWLEDGE_LOG_REL_PATH } from "./constants.js";
|
|
4
|
+
import { exists, writeFileSafe } from "./fs-utils.js";
|
|
5
|
+
export class KnowledgeStoreError extends Error {
|
|
136
6
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const KNOWLEDGE_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
|
|
140
|
-
const LEGACY_KNOWLEDGE_UNIVERSALITY_SET = new Set(["project", "personal", "universal"]);
|
|
141
|
-
const LEGACY_KNOWLEDGE_MATURITY_SET = new Set(["raw", "lifted-to-rule", "lifted-to-enforcement"]);
|
|
142
|
-
const KNOWLEDGE_SOURCE_SET = new Set([
|
|
143
|
-
"stage",
|
|
144
|
-
"retro",
|
|
145
|
-
"compound",
|
|
146
|
-
"idea",
|
|
147
|
-
"manual"
|
|
148
|
-
]);
|
|
149
|
-
const FLOW_STAGE_SET = new Set(FLOW_STAGES);
|
|
150
|
-
const KNOWLEDGE_REQUIRED_KEYS = [
|
|
151
|
-
"type",
|
|
152
|
-
"trigger",
|
|
153
|
-
"action",
|
|
154
|
-
"confidence",
|
|
155
|
-
"stage",
|
|
156
|
-
"origin_stage",
|
|
157
|
-
"frequency",
|
|
158
|
-
"created",
|
|
159
|
-
"first_seen_ts",
|
|
160
|
-
"last_seen_ts",
|
|
161
|
-
"project"
|
|
162
|
-
];
|
|
163
|
-
const KNOWLEDGE_ALLOWED_KEYS = new Set(KNOWLEDGE_REQUIRED_KEYS);
|
|
164
|
-
// Legacy keys are accepted for backwards compatibility when reading historical
|
|
165
|
-
// knowledge entries, but no longer required for newly materialized rows.
|
|
166
|
-
KNOWLEDGE_ALLOWED_KEYS.add("domain");
|
|
167
|
-
KNOWLEDGE_ALLOWED_KEYS.add("origin_run");
|
|
168
|
-
KNOWLEDGE_ALLOWED_KEYS.add("universality");
|
|
169
|
-
KNOWLEDGE_ALLOWED_KEYS.add("maturity");
|
|
170
|
-
KNOWLEDGE_ALLOWED_KEYS.add("source");
|
|
171
|
-
KNOWLEDGE_ALLOWED_KEYS.add("severity");
|
|
172
|
-
KNOWLEDGE_ALLOWED_KEYS.add("supersedes");
|
|
173
|
-
KNOWLEDGE_ALLOWED_KEYS.add("superseded_by");
|
|
174
|
-
function keyAllowedInKnowledgeEntry(key) {
|
|
175
|
-
return KNOWLEDGE_ALLOWED_KEYS.has(key);
|
|
7
|
+
export function knowledgeLogPath(projectRoot) {
|
|
8
|
+
return path.join(projectRoot, KNOWLEDGE_LOG_REL_PATH);
|
|
176
9
|
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
trigger: obj.trigger,
|
|
181
|
-
action: obj.action,
|
|
182
|
-
confidence: obj.confidence,
|
|
183
|
-
stage: obj.stage,
|
|
184
|
-
origin_stage: obj.origin_stage,
|
|
185
|
-
frequency: obj.frequency,
|
|
186
|
-
created: obj.created,
|
|
187
|
-
first_seen_ts: obj.first_seen_ts,
|
|
188
|
-
last_seen_ts: obj.last_seen_ts,
|
|
189
|
-
project: obj.project
|
|
190
|
-
};
|
|
191
|
-
if (obj.severity !== undefined) {
|
|
192
|
-
normalized.severity = obj.severity;
|
|
10
|
+
function assertEntry(value) {
|
|
11
|
+
if (typeof value !== "object" || value === null) {
|
|
12
|
+
throw new KnowledgeStoreError("Knowledge entry must be an object.");
|
|
193
13
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
function knowledgePath(projectRoot) {
|
|
200
|
-
return path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
|
|
201
|
-
}
|
|
202
|
-
function knowledgeLockPath(projectRoot) {
|
|
203
|
-
return path.join(projectRoot, RUNTIME_ROOT, "state", ".knowledge.lock");
|
|
204
|
-
}
|
|
205
|
-
function normalizeUtcIso(iso) {
|
|
206
|
-
return iso.replace(/\.\d{3}Z$/u, "Z");
|
|
207
|
-
}
|
|
208
|
-
function nowUtcIso() {
|
|
209
|
-
return normalizeUtcIso(new Date().toISOString());
|
|
210
|
-
}
|
|
211
|
-
function normalizeText(value) {
|
|
212
|
-
return value.trim().replace(/\s+/gu, " ").toLowerCase();
|
|
213
|
-
}
|
|
214
|
-
function dedupeKey(entry) {
|
|
215
|
-
return [
|
|
216
|
-
entry.type,
|
|
217
|
-
normalizeText(entry.trigger),
|
|
218
|
-
normalizeText(entry.action),
|
|
219
|
-
entry.stage ?? "null",
|
|
220
|
-
entry.origin_stage ?? "null",
|
|
221
|
-
entry.project === null ? "null" : normalizeText(entry.project),
|
|
222
|
-
entry.source === undefined || entry.source === null ? "null" : entry.source,
|
|
223
|
-
entry.severity === undefined ? "none" : entry.severity
|
|
224
|
-
].join("|");
|
|
225
|
-
}
|
|
226
|
-
function emptyKnowledgeSnapshot() {
|
|
227
|
-
return {
|
|
228
|
-
lines: [],
|
|
229
|
-
entries: [],
|
|
230
|
-
malformedLines: 0,
|
|
231
|
-
keyToIndex: new Map(),
|
|
232
|
-
entryByIndex: new Map()
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function parseKnowledgeSnapshot(raw) {
|
|
236
|
-
const lines = stripBom(raw).split(/\r?\n/u);
|
|
237
|
-
const entries = [];
|
|
238
|
-
const keyToIndex = new Map();
|
|
239
|
-
const entryByIndex = new Map();
|
|
240
|
-
let malformedLines = 0;
|
|
241
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
242
|
-
const trimmed = lines[i].trim();
|
|
243
|
-
if (trimmed.length === 0)
|
|
244
|
-
continue;
|
|
245
|
-
try {
|
|
246
|
-
const parsed = JSON.parse(trimmed);
|
|
247
|
-
const validated = validateKnowledgeEntry(parsed);
|
|
248
|
-
if (!validated.ok) {
|
|
249
|
-
malformedLines += 1;
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
const entry = normalizeParsedKnowledgeEntry(parsed);
|
|
253
|
-
entries.push(entry);
|
|
254
|
-
const key = dedupeKey(entry);
|
|
255
|
-
if (!keyToIndex.has(key)) {
|
|
256
|
-
keyToIndex.set(key, i);
|
|
257
|
-
}
|
|
258
|
-
entryByIndex.set(i, entry);
|
|
259
|
-
}
|
|
260
|
-
catch {
|
|
261
|
-
malformedLines += 1;
|
|
14
|
+
const entry = value;
|
|
15
|
+
for (const key of ["slug", "ship_commit", "shipped_at"]) {
|
|
16
|
+
if (typeof entry[key] !== "string" || entry[key].length === 0) {
|
|
17
|
+
throw new KnowledgeStoreError(`Knowledge entry must include string ${key}.`);
|
|
262
18
|
}
|
|
263
19
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
entries,
|
|
267
|
-
malformedLines,
|
|
268
|
-
keyToIndex,
|
|
269
|
-
entryByIndex
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
async function readKnowledgeSnapshot(filePath) {
|
|
273
|
-
try {
|
|
274
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
275
|
-
return parseKnowledgeSnapshot(raw);
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
const code = error?.code;
|
|
279
|
-
if (code === "ENOENT") {
|
|
280
|
-
return emptyKnowledgeSnapshot();
|
|
281
|
-
}
|
|
282
|
-
throw error;
|
|
20
|
+
if (typeof entry.signals !== "object" || entry.signals === null) {
|
|
21
|
+
throw new KnowledgeStoreError("Knowledge entry must include a `signals` object.");
|
|
283
22
|
}
|
|
284
23
|
}
|
|
285
|
-
function
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
frequency: mergedFrequency,
|
|
293
|
-
last_seen_ts: mergedLastSeen
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
function isIsoUtcTimestamp(value) {
|
|
297
|
-
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/u.test(value);
|
|
298
|
-
}
|
|
299
|
-
function isNullableString(value) {
|
|
300
|
-
return value === null || typeof value === "string";
|
|
301
|
-
}
|
|
302
|
-
function isNullableStage(value) {
|
|
303
|
-
return value === null || (typeof value === "string" && FLOW_STAGE_SET.has(value));
|
|
304
|
-
}
|
|
305
|
-
export function validateKnowledgeEntry(entry) {
|
|
306
|
-
const errors = [];
|
|
307
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
308
|
-
return { ok: false, errors: ["Knowledge entry must be a JSON object."] };
|
|
309
|
-
}
|
|
310
|
-
const obj = entry;
|
|
311
|
-
for (const key of Object.keys(obj)) {
|
|
312
|
-
if (!keyAllowedInKnowledgeEntry(key)) {
|
|
313
|
-
errors.push(`Unknown key "${key}" in knowledge entry.`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
for (const key of KNOWLEDGE_REQUIRED_KEYS) {
|
|
317
|
-
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
318
|
-
errors.push(`Missing required key "${key}".`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (!KNOWLEDGE_TYPE_SET.has(obj.type)) {
|
|
322
|
-
errors.push("type must be one of: rule, pattern, lesson, compound.");
|
|
323
|
-
}
|
|
324
|
-
if (typeof obj.trigger !== "string" || obj.trigger.trim().length === 0) {
|
|
325
|
-
errors.push("trigger must be a non-empty string.");
|
|
326
|
-
}
|
|
327
|
-
if (typeof obj.action !== "string" || obj.action.trim().length === 0) {
|
|
328
|
-
errors.push("action must be a non-empty string.");
|
|
329
|
-
}
|
|
330
|
-
if (!KNOWLEDGE_CONFIDENCE_SET.has(obj.confidence)) {
|
|
331
|
-
errors.push("confidence must be one of: high, medium, low.");
|
|
332
|
-
}
|
|
333
|
-
if (obj.severity !== undefined &&
|
|
334
|
-
(typeof obj.severity !== "string" || !KNOWLEDGE_SEVERITY_SET.has(obj.severity))) {
|
|
335
|
-
errors.push("severity must be one of: critical, important, suggestion.");
|
|
336
|
-
}
|
|
337
|
-
if (obj.domain !== undefined && !isNullableString(obj.domain)) {
|
|
338
|
-
errors.push("domain must be string or null.");
|
|
339
|
-
}
|
|
340
|
-
if (!isNullableStage(obj.stage)) {
|
|
341
|
-
errors.push(`stage must be one of ${FLOW_STAGES.join(", ")} or null.`);
|
|
342
|
-
}
|
|
343
|
-
if (!isNullableStage(obj.origin_stage)) {
|
|
344
|
-
errors.push(`origin_stage must be one of ${FLOW_STAGES.join(", ")} or null.`);
|
|
345
|
-
}
|
|
346
|
-
const originRun = obj.origin_run;
|
|
347
|
-
if (originRun !== undefined && !isNullableString(originRun)) {
|
|
348
|
-
errors.push("origin_run must be string or null.");
|
|
24
|
+
export async function appendKnowledgeEntry(projectRoot, entry) {
|
|
25
|
+
assertEntry(entry);
|
|
26
|
+
const target = knowledgeLogPath(projectRoot);
|
|
27
|
+
const line = `${JSON.stringify(entry)}\n`;
|
|
28
|
+
if (!(await exists(target))) {
|
|
29
|
+
await writeFileSafe(target, line);
|
|
30
|
+
return;
|
|
349
31
|
}
|
|
350
|
-
|
|
351
|
-
!Number.isInteger(obj.frequency) ||
|
|
352
|
-
obj.frequency < 1) {
|
|
353
|
-
errors.push("frequency must be an integer >= 1.");
|
|
354
|
-
}
|
|
355
|
-
if (obj.universality !== undefined &&
|
|
356
|
-
(typeof obj.universality !== "string" || !LEGACY_KNOWLEDGE_UNIVERSALITY_SET.has(obj.universality))) {
|
|
357
|
-
errors.push("universality must be one of: project, personal, universal.");
|
|
358
|
-
}
|
|
359
|
-
if (obj.maturity !== undefined &&
|
|
360
|
-
(typeof obj.maturity !== "string" || !LEGACY_KNOWLEDGE_MATURITY_SET.has(obj.maturity))) {
|
|
361
|
-
errors.push("maturity must be one of: raw, lifted-to-rule, lifted-to-enforcement.");
|
|
362
|
-
}
|
|
363
|
-
for (const timestampField of ["created", "first_seen_ts", "last_seen_ts"]) {
|
|
364
|
-
const value = obj[timestampField];
|
|
365
|
-
if (typeof value !== "string" || !isIsoUtcTimestamp(value)) {
|
|
366
|
-
errors.push(`${timestampField} must be ISO UTC (YYYY-MM-DDTHH:MM:SSZ).`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (!isNullableString(obj.project)) {
|
|
370
|
-
errors.push("project must be string or null.");
|
|
371
|
-
}
|
|
372
|
-
if (obj.supersedes !== undefined) {
|
|
373
|
-
if (!Array.isArray(obj.supersedes) ||
|
|
374
|
-
obj.supersedes.length === 0 ||
|
|
375
|
-
obj.supersedes.some((value) => typeof value !== "string" || value.trim().length === 0)) {
|
|
376
|
-
errors.push("supersedes must be a non-empty array of strings when present.");
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
if (obj.superseded_by !== undefined &&
|
|
380
|
-
(typeof obj.superseded_by !== "string" || obj.superseded_by.trim().length === 0)) {
|
|
381
|
-
errors.push("superseded_by must be a non-empty string when present.");
|
|
382
|
-
}
|
|
383
|
-
const sourceAllowed = obj.source === undefined ||
|
|
384
|
-
obj.source === null ||
|
|
385
|
-
(typeof obj.source === "string" &&
|
|
386
|
-
KNOWLEDGE_SOURCE_SET.has(obj.source));
|
|
387
|
-
if (!sourceAllowed) {
|
|
388
|
-
errors.push("source must be one of: stage, retro, compound, idea, manual, or null.");
|
|
389
|
-
}
|
|
390
|
-
return { ok: errors.length === 0, errors };
|
|
32
|
+
await fs.appendFile(target, line, "utf8");
|
|
391
33
|
}
|
|
392
|
-
export function
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
const originStage = seed.origin_stage ?? defaults.originStage ?? stage ?? null;
|
|
396
|
-
const source = seed.source ?? defaults.source ?? null;
|
|
397
|
-
const entry = {
|
|
398
|
-
type: seed.type,
|
|
399
|
-
trigger: seed.trigger.trim(),
|
|
400
|
-
action: seed.action.trim(),
|
|
401
|
-
confidence: seed.confidence,
|
|
402
|
-
stage,
|
|
403
|
-
origin_stage: originStage,
|
|
404
|
-
frequency: seed.frequency ?? 1,
|
|
405
|
-
created: normalizeUtcIso(seed.created ?? now),
|
|
406
|
-
first_seen_ts: normalizeUtcIso(seed.first_seen_ts ?? now),
|
|
407
|
-
last_seen_ts: normalizeUtcIso(seed.last_seen_ts ?? now),
|
|
408
|
-
project: seed.project ?? defaults.project ?? null
|
|
409
|
-
};
|
|
410
|
-
if (seed.severity !== undefined) {
|
|
411
|
-
entry.severity = seed.severity;
|
|
412
|
-
}
|
|
413
|
-
if (source !== null) {
|
|
414
|
-
entry.source = source;
|
|
415
|
-
}
|
|
416
|
-
return entry;
|
|
417
|
-
}
|
|
418
|
-
export async function readKnowledgeSafely(projectRoot, options = {}) {
|
|
419
|
-
const filePath = knowledgePath(projectRoot);
|
|
420
|
-
const read = async () => {
|
|
421
|
-
const snapshot = await readKnowledgeSnapshot(filePath);
|
|
422
|
-
return {
|
|
423
|
-
entries: snapshot.entries,
|
|
424
|
-
malformedLines: snapshot.malformedLines
|
|
425
|
-
};
|
|
426
|
-
};
|
|
427
|
-
if (options.lockAware === false) {
|
|
428
|
-
return read();
|
|
429
|
-
}
|
|
430
|
-
return withDirectoryLock(knowledgeLockPath(projectRoot), read);
|
|
431
|
-
}
|
|
432
|
-
export async function appendKnowledge(projectRoot, seeds, defaults = {}) {
|
|
433
|
-
if (seeds.length === 0) {
|
|
434
|
-
return { appended: 0, skippedDuplicates: 0, invalid: 0, errors: [], appendedEntries: [] };
|
|
435
|
-
}
|
|
436
|
-
const filePath = knowledgePath(projectRoot);
|
|
437
|
-
const errors = [];
|
|
438
|
-
const materialized = [];
|
|
439
|
-
for (let i = 0; i < seeds.length; i += 1) {
|
|
440
|
-
const seed = seeds[i];
|
|
441
|
-
const entry = materializeKnowledgeEntry(seed, defaults);
|
|
442
|
-
const validated = validateKnowledgeEntry(entry);
|
|
443
|
-
if (!validated.ok) {
|
|
444
|
-
errors.push(`entry #${i + 1}: ${validated.errors.join(" ")}`);
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
materialized.push(entry);
|
|
448
|
-
}
|
|
449
|
-
let skippedDuplicates = 0;
|
|
450
|
-
const appendedEntries = [];
|
|
451
|
-
await withDirectoryLock(knowledgeLockPath(projectRoot), async () => {
|
|
452
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
453
|
-
const snapshot = await readKnowledgeSnapshot(filePath);
|
|
454
|
-
const updatedByIndex = new Map();
|
|
455
|
-
const batchEntries = new Map();
|
|
456
|
-
for (const entry of materialized) {
|
|
457
|
-
const key = dedupeKey(entry);
|
|
458
|
-
const existingIndex = snapshot.keyToIndex.get(key);
|
|
459
|
-
if (existingIndex !== undefined) {
|
|
460
|
-
skippedDuplicates += 1;
|
|
461
|
-
const base = updatedByIndex.get(existingIndex) ?? snapshot.entryByIndex.get(existingIndex);
|
|
462
|
-
if (base) {
|
|
463
|
-
updatedByIndex.set(existingIndex, mergeKnowledgeOccurrence(base, entry));
|
|
464
|
-
}
|
|
465
|
-
continue;
|
|
466
|
-
}
|
|
467
|
-
const existingBatchEntry = batchEntries.get(key);
|
|
468
|
-
if (existingBatchEntry) {
|
|
469
|
-
skippedDuplicates += 1;
|
|
470
|
-
batchEntries.set(key, mergeKnowledgeOccurrence(existingBatchEntry, entry));
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
batchEntries.set(key, { ...entry });
|
|
474
|
-
}
|
|
475
|
-
appendedEntries.push(...batchEntries.values());
|
|
476
|
-
if (updatedByIndex.size === 0 && batchEntries.size === 0) {
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
const rewrittenLines = snapshot.lines.map((line, index) => {
|
|
480
|
-
const updated = updatedByIndex.get(index);
|
|
481
|
-
return updated ? JSON.stringify(updated) : line;
|
|
482
|
-
}).filter((line) => line.trim().length > 0);
|
|
483
|
-
const linesToWrite = [
|
|
484
|
-
...rewrittenLines,
|
|
485
|
-
...Array.from(batchEntries.values(), (entry) => JSON.stringify(entry))
|
|
486
|
-
];
|
|
487
|
-
if (linesToWrite.length > 0) {
|
|
488
|
-
await fs.writeFile(filePath, `${linesToWrite.join("\n")}\n`, "utf8");
|
|
489
|
-
}
|
|
490
|
-
});
|
|
491
|
-
return {
|
|
492
|
-
appended: appendedEntries.length,
|
|
493
|
-
skippedDuplicates,
|
|
494
|
-
invalid: errors.length,
|
|
495
|
-
errors,
|
|
496
|
-
appendedEntries
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
const SHORT_TECHNICAL_TOKEN_SET = new Set(["ci", "db", "ui", "qa", "ux"]);
|
|
500
|
-
function tokenizeText(value) {
|
|
501
|
-
if (!value)
|
|
502
|
-
return [];
|
|
503
|
-
const tokens = [];
|
|
504
|
-
const matches = value.matchAll(/[A-Za-z0-9]+/gu);
|
|
505
|
-
for (const match of matches) {
|
|
506
|
-
const raw = match[0] ?? "";
|
|
507
|
-
const normalized = raw.toLowerCase();
|
|
508
|
-
if (normalized.length >= 3) {
|
|
509
|
-
tokens.push(normalized);
|
|
510
|
-
continue;
|
|
511
|
-
}
|
|
512
|
-
if (/^[A-Z]{2}$/u.test(raw) || SHORT_TECHNICAL_TOKEN_SET.has(normalized)) {
|
|
513
|
-
tokens.push(normalized);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return tokens;
|
|
517
|
-
}
|
|
518
|
-
function uniqueTokens(values) {
|
|
519
|
-
return [...new Set(values)];
|
|
520
|
-
}
|
|
521
|
-
function pathTokens(paths) {
|
|
522
|
-
if (!Array.isArray(paths) || paths.length === 0)
|
|
34
|
+
export async function readKnowledgeLog(projectRoot) {
|
|
35
|
+
const target = knowledgeLogPath(projectRoot);
|
|
36
|
+
if (!(await exists(target)))
|
|
523
37
|
return [];
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
score += 2;
|
|
556
|
-
if (entry.confidence === "medium")
|
|
557
|
-
score += 1;
|
|
558
|
-
if (entry.frequency >= 3)
|
|
559
|
-
score += 1;
|
|
560
|
-
const searchable = [
|
|
561
|
-
...tokenizeText(entry.trigger),
|
|
562
|
-
...tokenizeText(entry.action),
|
|
563
|
-
...tokenizeText(entry.project)
|
|
564
|
-
];
|
|
565
|
-
const searchSet = new Set(searchable);
|
|
566
|
-
let contextualScore = 0;
|
|
567
|
-
for (const token of branchTokens) {
|
|
568
|
-
if (searchSet.has(token))
|
|
569
|
-
contextualScore += 2;
|
|
570
|
-
}
|
|
571
|
-
for (const token of diffTokens) {
|
|
572
|
-
if (searchSet.has(token))
|
|
573
|
-
contextualScore += 2;
|
|
574
|
-
}
|
|
575
|
-
for (const token of gateTokens) {
|
|
576
|
-
if (searchSet.has(token))
|
|
577
|
-
contextualScore += 2;
|
|
578
|
-
}
|
|
579
|
-
score += contextualScore;
|
|
580
|
-
if (stage && entry.stage === null && stageScore === 0 && contextualScore < 4) {
|
|
581
|
-
score = 0;
|
|
582
|
-
}
|
|
583
|
-
return {
|
|
584
|
-
index,
|
|
585
|
-
score,
|
|
586
|
-
entry
|
|
587
|
-
};
|
|
588
|
-
});
|
|
589
|
-
ranked.sort((a, b) => {
|
|
590
|
-
if (b.score !== a.score)
|
|
591
|
-
return b.score - a.score;
|
|
592
|
-
const bySeen = Date.parse(b.entry.last_seen_ts) - Date.parse(a.entry.last_seen_ts);
|
|
593
|
-
if (!Number.isNaN(bySeen) && bySeen !== 0)
|
|
594
|
-
return bySeen;
|
|
595
|
-
if (b.entry.frequency !== a.entry.frequency)
|
|
596
|
-
return b.entry.frequency - a.entry.frequency;
|
|
597
|
-
return b.index - a.index;
|
|
598
|
-
});
|
|
599
|
-
return ranked
|
|
600
|
-
.filter((row) => row.score > 0)
|
|
601
|
-
.slice(0, limit)
|
|
602
|
-
.map((row) => row.entry);
|
|
38
|
+
const raw = await fs.readFile(target, "utf8");
|
|
39
|
+
const lines = raw.split(/\r?\n/u).filter((line) => line.trim().length > 0);
|
|
40
|
+
const entries = [];
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(line);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
throw new KnowledgeStoreError(`Invalid JSON line in knowledge.jsonl: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
assertEntry(parsed);
|
|
50
|
+
entries.push(parsed);
|
|
51
|
+
}
|
|
52
|
+
return entries;
|
|
53
|
+
}
|
|
54
|
+
export async function findRefiningChain(projectRoot, slug) {
|
|
55
|
+
const all = await readKnowledgeLog(projectRoot);
|
|
56
|
+
const bySlug = new Map();
|
|
57
|
+
for (const entry of all)
|
|
58
|
+
bySlug.set(entry.slug, entry);
|
|
59
|
+
const chain = [];
|
|
60
|
+
let cursor = slug;
|
|
61
|
+
const seen = new Set();
|
|
62
|
+
while (cursor !== null && cursor !== undefined && bySlug.has(cursor) && !seen.has(cursor)) {
|
|
63
|
+
const found = bySlug.get(cursor);
|
|
64
|
+
chain.push(found);
|
|
65
|
+
seen.add(cursor);
|
|
66
|
+
cursor = found.refines ?? null;
|
|
67
|
+
}
|
|
68
|
+
return chain;
|
|
603
69
|
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
type Stream = NodeJS.WriteStream | {
|
|
2
|
+
write: (chunk: string) => unknown;
|
|
3
|
+
};
|
|
4
|
+
export declare function configureLogger(out: Stream, err: Stream): void;
|
|
5
|
+
export declare function info(message: string): void;
|
|
6
|
+
export declare function warn(message: string): void;
|
|
7
|
+
export declare function error(message: string): void;
|
|
8
|
+
export {};
|