@jmtrin/kevin-core 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Archiver.d.ts +42 -0
- package/dist/Archiver.js +83 -0
- package/dist/ArtifactWriter.d.ts +93 -0
- package/dist/ArtifactWriter.js +291 -0
- package/dist/CausalChain.d.ts +22 -0
- package/dist/CausalChain.js +197 -0
- package/dist/ChatBridge.d.ts +41 -0
- package/dist/ChatBridge.js +103 -0
- package/dist/ConflictDetector.d.ts +35 -0
- package/dist/ConflictDetector.js +260 -0
- package/dist/ContextInjector.d.ts +222 -0
- package/dist/ContextInjector.js +533 -0
- package/dist/ConventionMiner.d.ts +35 -0
- package/dist/ConventionMiner.js +242 -0
- package/dist/Curator.d.ts +118 -0
- package/dist/Curator.js +326 -0
- package/dist/DashboardHtml.d.ts +5 -0
- package/dist/DashboardHtml.js +180 -0
- package/dist/Feedback.d.ts +67 -0
- package/dist/Feedback.js +120 -0
- package/dist/HookLiveness.d.ts +88 -0
- package/dist/HookLiveness.js +322 -0
- package/dist/InjectionLedger.d.ts +98 -0
- package/dist/InjectionLedger.js +295 -0
- package/dist/LessonFixer.d.ts +44 -0
- package/dist/LessonFixer.js +45 -0
- package/dist/Materializer.d.ts +84 -0
- package/dist/Materializer.js +181 -0
- package/dist/MemoryService.d.ts +360 -0
- package/dist/MemoryService.js +1242 -0
- package/dist/Migrate.d.ts +24 -0
- package/dist/Migrate.js +285 -0
- package/dist/PatternMiner.d.ts +49 -0
- package/dist/PatternMiner.js +132 -0
- package/dist/QualityGate.d.ts +124 -0
- package/dist/QualityGate.js +119 -0
- package/dist/Reflector.d.ts +91 -0
- package/dist/Reflector.js +307 -0
- package/dist/RepoIdentity.d.ts +124 -0
- package/dist/RepoIdentity.js +300 -0
- package/dist/RepoTruth.d.ts +80 -0
- package/dist/RepoTruth.js +599 -0
- package/dist/Retrospective.d.ts +18 -0
- package/dist/Retrospective.js +216 -0
- package/dist/SharedLayer.d.ts +159 -0
- package/dist/SharedLayer.js +462 -0
- package/dist/Store.d.ts +29 -0
- package/dist/Store.js +50 -0
- package/dist/ToolCallObserver.d.ts +32 -0
- package/dist/ToolCallObserver.js +201 -0
- package/dist/TuiActions.d.ts +43 -0
- package/dist/TuiActions.js +181 -0
- package/dist/TuiSnapshots.d.ts +24 -0
- package/dist/TuiSnapshots.js +159 -0
- package/dist/capabilities.d.ts +6 -0
- package/dist/capabilities.js +1 -0
- package/dist/columns.d.ts +11 -0
- package/dist/columns.js +54 -0
- package/dist/confidence.d.ts +8 -0
- package/dist/confidence.js +34 -0
- package/dist/contract.d.ts +68 -0
- package/dist/contract.js +430 -0
- package/dist/diff.d.ts +8 -0
- package/dist/diff.js +182 -0
- package/dist/env.d.ts +5 -0
- package/dist/env.js +12 -0
- package/dist/escape.d.ts +39 -0
- package/dist/escape.js +60 -0
- package/dist/fingerprint.d.ts +27 -0
- package/dist/fingerprint.js +73 -0
- package/dist/host.d.ts +18 -0
- package/dist/host.js +7 -0
- package/dist/idle-pipeline.d.ts +9 -0
- package/dist/idle-pipeline.js +44 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +109 -0
- package/dist/inferability.d.ts +32 -0
- package/dist/inferability.js +88 -0
- package/dist/kevin_approve.d.ts +34 -0
- package/dist/kevin_approve.js +52 -0
- package/dist/kevin_audit.d.ts +230 -0
- package/dist/kevin_audit.js +631 -0
- package/dist/kevin_bench.d.ts +11 -0
- package/dist/kevin_bench.js +83 -0
- package/dist/kevin_conflicts.d.ts +9 -0
- package/dist/kevin_conflicts.js +50 -0
- package/dist/kevin_contract.d.ts +8 -0
- package/dist/kevin_contract.js +59 -0
- package/dist/kevin_doctor.d.ts +63 -0
- package/dist/kevin_doctor.js +197 -0
- package/dist/kevin_facts.d.ts +42 -0
- package/dist/kevin_facts.js +36 -0
- package/dist/kevin_forget.d.ts +33 -0
- package/dist/kevin_forget.js +260 -0
- package/dist/kevin_native.d.ts +29 -0
- package/dist/kevin_native.js +79 -0
- package/dist/kevin_propose.d.ts +23 -0
- package/dist/kevin_propose.js +14 -0
- package/dist/kevin_publish.d.ts +38 -0
- package/dist/kevin_publish.js +18 -0
- package/dist/kevin_why.d.ts +23 -0
- package/dist/kevin_why.js +111 -0
- package/dist/memory-format.d.ts +20 -0
- package/dist/memory-format.js +59 -0
- package/dist/metrics.d.ts +97 -0
- package/dist/metrics.js +288 -0
- package/dist/migrations/001_initial.sql +92 -0
- package/dist/migrations/002_indexes.sql +14 -0
- package/dist/migrations/003_v02_signal.sql +58 -0
- package/dist/migrations/004_v03_knowledge.sql +138 -0
- package/dist/migrations/005_v04_signal.sql +57 -0
- package/dist/migrations/006_v05_glassbox.sql +118 -0
- package/dist/migrations/007_v06_pull.sql +145 -0
- package/dist/migrations/008_v07_truth.sql +124 -0
- package/dist/migrations/009_v08_team.sql +100 -0
- package/dist/migrations/010_v09_native.sql +78 -0
- package/dist/migrations/011_v10_proven.sql +69 -0
- package/dist/migrations/012_v11_drift.sql +24 -0
- package/dist/native.d.ts +23 -0
- package/dist/native.js +1 -0
- package/dist/okf-export.d.ts +3 -0
- package/dist/okf-export.js +134 -0
- package/dist/okf-import.d.ts +76 -0
- package/dist/okf-import.js +271 -0
- package/dist/okf.d.ts +107 -0
- package/dist/okf.js +303 -0
- package/dist/perf.d.ts +35 -0
- package/dist/perf.js +174 -0
- package/dist/query-tokenizer.d.ts +13 -0
- package/dist/query-tokenizer.js +133 -0
- package/dist/redact.d.ts +2 -0
- package/dist/redact.js +18 -0
- package/dist/replay-types.d.ts +69 -0
- package/dist/replay-types.js +156 -0
- package/dist/replay.d.ts +44 -0
- package/dist/replay.js +215 -0
- package/dist/sqlite-adapter.d.ts +12 -0
- package/dist/sqlite-adapter.js +78 -0
- package/dist/time-ms.d.ts +1 -0
- package/dist/time-ms.js +16 -0
- package/dist/tui-types.d.ts +59 -0
- package/dist/tui-types.js +4 -0
- package/dist/uuid.d.ts +1 -0
- package/dist/uuid.js +50 -0
- package/package.json +28 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { SUGGESTIONS } from "./Reflector.js";
|
|
2
|
+
/**
|
|
3
|
+
* v0.4.0 (K4-003/K4-004 / plan §5.1) — QualityGate.
|
|
4
|
+
*
|
|
5
|
+
* Pure, NO-LLM predicates deciding whether a lesson is worth injecting into
|
|
6
|
+
* the model's prompt. Principle 11: never inject noise.
|
|
7
|
+
*
|
|
8
|
+
* - `evaluate` — classifies a lesson (rescued errorType, non-generic
|
|
9
|
+
* suggestion, isActionable, strength).
|
|
10
|
+
* - `rescueErrorType` — a dispatched code overrides the coarse
|
|
11
|
+
* `errorType` (e.g. `'unknown'` → `'TS2304'`), fixing the defect where
|
|
12
|
+
* lessons said "fails with unknown" even when the Reflector DID identify
|
|
13
|
+
* the error code.
|
|
14
|
+
* - `canInject` — per-session + per-fingerprint admission rules (K4-004).
|
|
15
|
+
*/
|
|
16
|
+
/** The v0.1.x fallback suggestions. A lesson whose suggestion is one of these
|
|
17
|
+
* (and has no dispatched code) carries no actionable information. */
|
|
18
|
+
export const GENERIC_SUGGESTIONS = new Set(Object.values(SUGGESTIONS));
|
|
19
|
+
export function isGenericSuggestion(suggestion) {
|
|
20
|
+
return GENERIC_SUGGESTIONS.has(suggestion);
|
|
21
|
+
}
|
|
22
|
+
export const QualityGate = {
|
|
23
|
+
/**
|
|
24
|
+
* A dispatched code always overrides the coarse errorType for display:
|
|
25
|
+
* `'unknown'` + `TS2304` → `'TS2304'`.
|
|
26
|
+
*/
|
|
27
|
+
rescueErrorType(dispatch, errorType) {
|
|
28
|
+
if (dispatch?.code)
|
|
29
|
+
return dispatch.code;
|
|
30
|
+
return errorType;
|
|
31
|
+
},
|
|
32
|
+
/** Classify a lesson per §5.1 rule 1 (rescue) and the generic ban. */
|
|
33
|
+
evaluate(lesson, dispatch, errorType) {
|
|
34
|
+
const rescued = this.rescueErrorType(dispatch, errorType);
|
|
35
|
+
const generic = isGenericSuggestion(lesson.suggestion);
|
|
36
|
+
// §5.1 rule 2: the generic ban applies only when there is no
|
|
37
|
+
// dispatched code — a matched code always makes the lesson
|
|
38
|
+
// actionable (the lesson then carries a "Likely cause" hint).
|
|
39
|
+
const isActionable = !generic || dispatch?.code != null;
|
|
40
|
+
const strength = dispatch?.code != null || rescued !== "unknown" ? "strong" : "weak";
|
|
41
|
+
return {
|
|
42
|
+
errorType: rescued,
|
|
43
|
+
suggestion: lesson.suggestion,
|
|
44
|
+
isActionable,
|
|
45
|
+
strength,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
/**
|
|
49
|
+
* v0.4.0 (K4-004 / plan §5.1 rules 2-4) — admission gate evaluated at
|
|
50
|
+
* injection time. A lesson is injectable only when:
|
|
51
|
+
*
|
|
52
|
+
* 1. its memory id was not already injected this session (seen-set),
|
|
53
|
+
* 2. its memory is not `stale`,
|
|
54
|
+
* 3. its fingerprint has not recurred since injection (`recurrenceCount
|
|
55
|
+
* === 0` — a lesson that failed to prevent the error leaves the
|
|
56
|
+
* prompt),
|
|
57
|
+
* 4. it is actionable AND strong — weak lessons with generic suggestions
|
|
58
|
+
* are never injected (unless `qualityGateEnabled = false` for debug,
|
|
59
|
+
* K4-023).
|
|
60
|
+
*
|
|
61
|
+
* v0.5.0 (K5-006 / plan §5.2, D5-04) — the boolean form is a thin
|
|
62
|
+
* wrapper over `canInjectVerdict`; the reason is never discarded by
|
|
63
|
+
* callers that use the verdict form.
|
|
64
|
+
*/
|
|
65
|
+
canInject(memory, ctx, qualityGateEnabled = true) {
|
|
66
|
+
return this.canInjectVerdict(memory, ctx, qualityGateEnabled).allowed;
|
|
67
|
+
},
|
|
68
|
+
/**
|
|
69
|
+
* v0.5.0 (K5-006 / plan §5.2, D5-04) — the verdict form of `canInject`.
|
|
70
|
+
* Branch order preserves the v0.4.0 sequence with `ignored` inserted
|
|
71
|
+
* second:
|
|
72
|
+
*
|
|
73
|
+
* 0. `confidence < ctx.confidenceFloor` → `low_confidence` (v0.6.0,
|
|
74
|
+
* K6-022 — runs FIRST: it is the cheapest check, and a memory below
|
|
75
|
+
* the floor should not consume a seen-set slot it will never use)
|
|
76
|
+
* 1. seen this session → `seen_this_session`
|
|
77
|
+
* 2. `memory.ignored === true` → `ignored`
|
|
78
|
+
* 3. status !== 'active' → `not_active`
|
|
79
|
+
* 4. recurrence in session → `recurrence`
|
|
80
|
+
* 5. weak / not actionable → `weak` (skipped entirely when
|
|
81
|
+
* `qualityGateEnabled === false` — the debug flag bypasses the
|
|
82
|
+
* quality check only, never the seen/ignored/status/recurrence bans)
|
|
83
|
+
*
|
|
84
|
+
* `ignored` is a human verdict (D5-07) and is enforced even in debug
|
|
85
|
+
* mode: the flag excludes a memory from retrieval entirely, so the gate
|
|
86
|
+
* is only ever asked about it defensively.
|
|
87
|
+
*
|
|
88
|
+
* The floor comparison is `>=`-admissive: a memory exactly AT the floor
|
|
89
|
+
* passes the branch (asserted in K6-022). A floor of '0' admits
|
|
90
|
+
* everything the other five branches allow.
|
|
91
|
+
*/
|
|
92
|
+
canInjectVerdict(memory, ctx, qualityGateEnabled = true) {
|
|
93
|
+
if (memory.confidence !== undefined &&
|
|
94
|
+
ctx.confidenceFloor !== undefined &&
|
|
95
|
+
memory.confidence < ctx.confidenceFloor) {
|
|
96
|
+
return { allowed: false, reason: "low_confidence" };
|
|
97
|
+
}
|
|
98
|
+
if (ctx.seenThisSession.has(memory.id)) {
|
|
99
|
+
return { allowed: false, reason: "seen_this_session" };
|
|
100
|
+
}
|
|
101
|
+
if (memory.ignored === true) {
|
|
102
|
+
return { allowed: false, reason: "ignored" };
|
|
103
|
+
}
|
|
104
|
+
if (memory.status !== undefined && memory.status !== "active") {
|
|
105
|
+
return { allowed: false, reason: "not_active" };
|
|
106
|
+
}
|
|
107
|
+
if (ctx.recurrenceCount > 0) {
|
|
108
|
+
return { allowed: false, reason: "recurrence" };
|
|
109
|
+
}
|
|
110
|
+
if (!memory.isActionable || memory.strength === "weak") {
|
|
111
|
+
if (qualityGateEnabled) {
|
|
112
|
+
return { allowed: false, reason: "weak" };
|
|
113
|
+
}
|
|
114
|
+
// K4-023 debug mode: weak lessons are admitted but must be
|
|
115
|
+
// flagged by the caller (ContextInjector adds the marker).
|
|
116
|
+
}
|
|
117
|
+
return { allowed: true, reason: "ok" };
|
|
118
|
+
},
|
|
119
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { MemoryService } from "./MemoryService.js";
|
|
2
|
+
import type { Metrics } from "./metrics.js";
|
|
3
|
+
export interface ReflectionInput {
|
|
4
|
+
toolName: string;
|
|
5
|
+
argsSummary: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
stdout: string;
|
|
8
|
+
exitCode?: number;
|
|
9
|
+
errorType: string;
|
|
10
|
+
sessionId: string;
|
|
11
|
+
/** Optional project scope for v0.2.0 dedup + per-fingerprint throttle. */
|
|
12
|
+
projectId?: string | null;
|
|
13
|
+
/** v0.3.0 fix — callID of the failing tool_call. Used by onLinkError
|
|
14
|
+
* (see ReflectorOptions) to stamp tool_calls.error_fingerprint so the
|
|
15
|
+
* feedback loop can match recurrences by the SAME identity dimension
|
|
16
|
+
* the error memory uses. */
|
|
17
|
+
callID?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ReflectorOptions {
|
|
20
|
+
throttleMs?: number;
|
|
21
|
+
/** v0.3.0 (K3-018) — optional LLM enrichment callback. Default no-op. */
|
|
22
|
+
enrich?: (lesson: string, stderr: string, stdout: string) => Promise<string | null>;
|
|
23
|
+
/** v0.3.0 fix — link a failing tool_call to the stderr-based fingerprint
|
|
24
|
+
* the matching error memory uses. Default no-op. The caller (index.ts)
|
|
25
|
+
* provides an implementation that UPDATEs tool_calls.error_fingerprint
|
|
26
|
+
* for the given callID, so boost/penalize feedback queries stop missing
|
|
27
|
+
* recurrences by fingerprint mismatch. */
|
|
28
|
+
onLinkError?: (callID: string, fingerprint: string) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface HeuristicLessonInput {
|
|
31
|
+
toolName: string;
|
|
32
|
+
errorType: string;
|
|
33
|
+
/** v0.4.0 (K4-005) — errorType shown in the `fails with` slot after
|
|
34
|
+
* rescue: the dispatched code when one matched, else the coarse
|
|
35
|
+
* errorType. Suggestion lookup still uses `errorType` (the coarse
|
|
36
|
+
* category) so the category suggestion is kept even when a code
|
|
37
|
+
* overrides the display. */
|
|
38
|
+
displayErrorType?: string;
|
|
39
|
+
firstErrorLine: string;
|
|
40
|
+
/** v0.2.0 (K2-018) — optional pre-computed dispatch result. When absent,
|
|
41
|
+
* dispatch is performed on `firstErrorLine` alone. */
|
|
42
|
+
dispatched?: DispatchedLesson | null;
|
|
43
|
+
}
|
|
44
|
+
/** Result of the per-error-code deterministic rule dispatch (K2-018 / D2-09). */
|
|
45
|
+
export interface DispatchedLesson {
|
|
46
|
+
/** Stable short code captured from the source output (e.g. `TS2304`,
|
|
47
|
+
* `EADDRINUSE`, `F401`, the captured `Error: <Name>` class, the failing
|
|
48
|
+
* command string). `null` when no rule matches (fallback path). */
|
|
49
|
+
code: string | null;
|
|
50
|
+
/** Short deterministic hint, e.g. `import or typo`, `review syscall:
|
|
51
|
+
* EADDRINUSE`. `null` when `code` is null (the v0.1.x fallback path). */
|
|
52
|
+
hint: string | null;
|
|
53
|
+
}
|
|
54
|
+
export declare const ERROR_LINE_RE: RegExp;
|
|
55
|
+
export declare const STRONG_ERROR_RE: RegExp;
|
|
56
|
+
/**
|
|
57
|
+
* v0.1.x fallback table — keyed by `errorType`. RETAINED in v0.2.0 as the
|
|
58
|
+
* fallback for memos whose output does not match a deterministic code rule.
|
|
59
|
+
* The v0.2.0 per-error-code rule table below layers ON TOP of this fallback:
|
|
60
|
+
* when a code is matched, the hint is appended to the v0.1.x suggestion as a
|
|
61
|
+
* 'Likely cause:' line; when no code is matched, output is identical to v0.1.x.
|
|
62
|
+
*
|
|
63
|
+
* Exported since v0.4.0 (K4-003) so QualityGate can detect the generic
|
|
64
|
+
* fallback suggestions without duplicating the strings.
|
|
65
|
+
*/
|
|
66
|
+
export declare const SUGGESTIONS: Record<string, string>;
|
|
67
|
+
export declare const TS_CODE_RULES: Record<string, string>;
|
|
68
|
+
export declare class Reflector {
|
|
69
|
+
private memoryService;
|
|
70
|
+
private lastReflectionByFp;
|
|
71
|
+
private throttleMs;
|
|
72
|
+
private metrics;
|
|
73
|
+
private enrichFn;
|
|
74
|
+
private onLinkErrorFn;
|
|
75
|
+
constructor(memoryService: MemoryService, options?: ReflectorOptions, metrics?: Metrics | null);
|
|
76
|
+
invoke(input: ReflectionInput): Promise<string | null>;
|
|
77
|
+
generateHeuristicLesson(input: HeuristicLessonInput): string;
|
|
78
|
+
/**
|
|
79
|
+
* v0.2.0 (K2-018 / D2-09) — deterministic per-error-code rule dispatch.
|
|
80
|
+
* Pure regex sweep over `stderr + '\n' + stdout` with NO LLM hop. Returns
|
|
81
|
+
* a short stable `code` + `hint` pair when a known rule matches, or
|
|
82
|
+
* `{ code: null, hint: null }` for the v0.1.x fallback path (which keeps
|
|
83
|
+
* the v0.1.x `SUGGESTIONS[errorType]` suggestion verbatim).
|
|
84
|
+
*
|
|
85
|
+
* Exported for unit testing (K2-019).
|
|
86
|
+
*/
|
|
87
|
+
dispatchLesson(stderr: string, stdout: string, errorType: string): DispatchedLesson;
|
|
88
|
+
redactPaths(text: string): string;
|
|
89
|
+
redactSecrets(text: string): string;
|
|
90
|
+
private extractFirstErrorLine;
|
|
91
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { fingerprint as computeFingerprint } from "./fingerprint.js";
|
|
2
|
+
import { classify } from "./inferability.js";
|
|
3
|
+
import { redactPaths as redactPathsText } from "./redact.js";
|
|
4
|
+
const DEFAULT_THROTTLE_MS = 60_000;
|
|
5
|
+
const MAX_CONTENT_CHARS = 4096;
|
|
6
|
+
const MAX_ERROR_LINE_CHARS = 500;
|
|
7
|
+
const TRUNC_SUFFIX = "... [truncated]";
|
|
8
|
+
const CONTEXT_PREFIX = "\n\nContext:\n";
|
|
9
|
+
export const ERROR_LINE_RE = /\b(error|failed|fail|cannot find|cannot resolve|TS\d{4,}|exception|traceback|panic|fatal|referenceerror|typeerror|syntaxerror|command failed|non-zero exit)\b/i;
|
|
10
|
+
export const STRONG_ERROR_RE = /\b(cannot find|cannot resolve|TS\d{4,}|error TS\d|command failed|non-zero exit|exit code [1-9]\d*|traceback|referenceerror|typeerror|syntaxerror|fatal error|exception|failed to compile|build failed|compilation failed)\b/i;
|
|
11
|
+
/**
|
|
12
|
+
* v0.1.x fallback table — keyed by `errorType`. RETAINED in v0.2.0 as the
|
|
13
|
+
* fallback for memos whose output does not match a deterministic code rule.
|
|
14
|
+
* The v0.2.0 per-error-code rule table below layers ON TOP of this fallback:
|
|
15
|
+
* when a code is matched, the hint is appended to the v0.1.x suggestion as a
|
|
16
|
+
* 'Likely cause:' line; when no code is matched, output is identical to v0.1.x.
|
|
17
|
+
*
|
|
18
|
+
* Exported since v0.4.0 (K4-003) so QualityGate can detect the generic
|
|
19
|
+
* fallback suggestions without duplicating the strings.
|
|
20
|
+
*/
|
|
21
|
+
export const SUGGESTIONS = {
|
|
22
|
+
typecheck: "Verify types and imports before running.",
|
|
23
|
+
lint: "Run linter and fix warnings before committing.",
|
|
24
|
+
test: "Run tests and fix failures before proceeding.",
|
|
25
|
+
runtime: "Check error message and stack trace for root cause.",
|
|
26
|
+
timeout: "Check for infinite loops or long-running operations.",
|
|
27
|
+
unknown: "Review the error output for details.",
|
|
28
|
+
};
|
|
29
|
+
// --- v0.2.0 (K2-018) lesson v2 — per-error-code deterministic dispatch (D2-09).
|
|
30
|
+
// Pure TS, NO LLM hop. Order of dispatch matches the plan §B6.4 priority list:
|
|
31
|
+
// (1) TS\d{4,5} > (2) Python lint > (3) syscall > (4) generic `Error: <Name>` >
|
|
32
|
+
// (5) `Command "<cmd>" failed` > (6) v0.1.x SUGGESTIONS fallback.
|
|
33
|
+
// v0.2.0 (K2-018) — shared per-error-code rule table. Exported so
|
|
34
|
+
// kevin_why.ts reuses the SAME hints instead of duplicating them
|
|
35
|
+
// (bug #6).
|
|
36
|
+
// v0.4.0 (K4-022 / plan §5.4, D4-07) — expanded with the observed §3.3
|
|
37
|
+
// cases: TS2307 (cannot find module), TS2339/TS2305 (missing member),
|
|
38
|
+
// TS6133 (unused).
|
|
39
|
+
export const TS_CODE_RULES = {
|
|
40
|
+
"2304": "import or typo",
|
|
41
|
+
"2322": "type mismatch",
|
|
42
|
+
"2740": "missing or wrong property",
|
|
43
|
+
"2552": "undefined identifier",
|
|
44
|
+
"18047": "possibly null",
|
|
45
|
+
"2307": "install the dependency or add it to package.json before importing",
|
|
46
|
+
"2339": "check the imported surface for the correct member name",
|
|
47
|
+
"2305": "check the imported surface for the correct member name",
|
|
48
|
+
"6133": "remove the declaration or use it",
|
|
49
|
+
};
|
|
50
|
+
const TS_CODE_RE = /\bTS(\d{4,5})\b/i;
|
|
51
|
+
const PY_LINT_RE = /\b(ELIF\d{0,4})\b|\b(F\d{3,4})\b|flake8:\s+(\S+)/;
|
|
52
|
+
const SYSCALL_RE = /\b(EADDRINUSE|ENOENT|EACCES|EPERM)\b/;
|
|
53
|
+
// v0.4.0 (K4-022) — rust unresolved item/module (E0433/E0432).
|
|
54
|
+
const RUST_CODE_RE = /\b(E0433|E0432)\b/;
|
|
55
|
+
// v0.4.0 (K4-022) — shell command-not-found (`cmd: command not found`,
|
|
56
|
+
// `The term 'cmd' is not recognized ...`).
|
|
57
|
+
const COMMAND_NOT_FOUND_RE = /\b([a-zA-Z0-9_.-]+):\s+(?:command not found|The term\s+['"][^'"]+['"]\s+is not recognized)\b/i;
|
|
58
|
+
const GENERIC_ERROR_RE = /\bError:\s+(\w+)/;
|
|
59
|
+
const COMMAND_FAILED_RE = /Command\s+"([^"]+)"\s+failed/;
|
|
60
|
+
const SECRET_PATTERNS = [
|
|
61
|
+
/\b(API_KEY|SECRET|PASSWORD|TOKEN)\b\s*[=:]\s*\S+/gi,
|
|
62
|
+
/\bBearer\s+\S+/gi,
|
|
63
|
+
// BUG-013 — same narrowing as ToolCallObserver: the old
|
|
64
|
+
// `\btoken\s+\S+/gi` mangled harmless phrasing ("token budget",
|
|
65
|
+
// "token count") in stderr/stdout — corrupting stored summaries AND
|
|
66
|
+
// the fingerprints derived from the redacted text. Credential
|
|
67
|
+
// contexts only: named token variables WITH an assignment, and
|
|
68
|
+
// `token=<value>` / `token: <value>` assignments.
|
|
69
|
+
/\b(access_?token|auth_?token|api_?token)\b\s*[=:]\s*\S+/gi,
|
|
70
|
+
/\btoken\s*[=:]\s*\S+/gi,
|
|
71
|
+
];
|
|
72
|
+
const PATH_PATTERNS_DEPRECATED = null;
|
|
73
|
+
export class Reflector {
|
|
74
|
+
memoryService;
|
|
75
|
+
lastReflectionByFp = new Map();
|
|
76
|
+
throttleMs;
|
|
77
|
+
metrics;
|
|
78
|
+
enrichFn;
|
|
79
|
+
onLinkErrorFn;
|
|
80
|
+
constructor(memoryService, options, metrics) {
|
|
81
|
+
this.memoryService = memoryService;
|
|
82
|
+
this.throttleMs = options?.throttleMs ?? DEFAULT_THROTTLE_MS;
|
|
83
|
+
this.metrics = metrics ?? null;
|
|
84
|
+
this.enrichFn = options?.enrich ?? (async () => null);
|
|
85
|
+
this.onLinkErrorFn = options?.onLinkError ?? (() => { });
|
|
86
|
+
}
|
|
87
|
+
async invoke(input) {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const redactedStderr = this.redactSecrets(this.redactPaths(input.stderr));
|
|
90
|
+
const redactedStdout = this.redactSecrets(this.redactPaths(input.stdout));
|
|
91
|
+
const sourceOutput = redactedStderr.length > 0 ? redactedStderr : redactedStdout;
|
|
92
|
+
const firstErrorLine = this.extractFirstErrorLine(sourceOutput);
|
|
93
|
+
// v0.4.0 (K4-005) — dispatch once and reuse for the rescued
|
|
94
|
+
// errorType AND metadata.dispatch.
|
|
95
|
+
const dispatched = this.dispatchLesson(redactedStderr, redactedStdout, input.errorType);
|
|
96
|
+
const lesson = this.generateHeuristicLesson({
|
|
97
|
+
toolName: input.toolName,
|
|
98
|
+
errorType: input.errorType,
|
|
99
|
+
displayErrorType: dispatched.code ?? input.errorType,
|
|
100
|
+
firstErrorLine,
|
|
101
|
+
dispatched,
|
|
102
|
+
});
|
|
103
|
+
// v0.3.0 fix (bug #5) — the per-fingerprint throttle check runs
|
|
104
|
+
// BEFORE the optional LLM enrichment, so throttled repeats never
|
|
105
|
+
// waste an LLM call. The fingerprint is computed from the source
|
|
106
|
+
// output when present, otherwise from the PRE-enrichment lesson,
|
|
107
|
+
// keeping the identity stable and identical to the saved memory's.
|
|
108
|
+
const projectId = input.projectId ?? null;
|
|
109
|
+
const fpContent = sourceOutput.length > 0 ? sourceOutput : lesson;
|
|
110
|
+
const fp = computeFingerprint(fpContent, projectId ?? undefined);
|
|
111
|
+
// v0.3.0 fix — stamp the failing tool_call with the stderr-based
|
|
112
|
+
// fingerprint so the feedback loop's recurrence queries can match
|
|
113
|
+
// it (closes the fingerprint-mismatch bug). Runs BEFORE the
|
|
114
|
+
// throttle check: throttled repeats (K4-025 recurrences) still
|
|
115
|
+
// stamp — the throttle only skips the expensive enrichment/save,
|
|
116
|
+
// but the recurrence signal (settle's COUNT, QualityGate,
|
|
117
|
+
// penalizeRecurringReflectors) must see every failing call.
|
|
118
|
+
if (input.callID && this.onLinkErrorFn) {
|
|
119
|
+
try {
|
|
120
|
+
this.onLinkErrorFn(input.callID, fp);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// linking failure is non-blocking
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const last = this.lastReflectionByFp.get(fp) ?? 0;
|
|
127
|
+
if (now - last < this.throttleMs) {
|
|
128
|
+
this.metrics?.incr("reflections_throttled", 1);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
this.lastReflectionByFp.set(fp, now);
|
|
132
|
+
const configurableMemoryService = this.memoryService;
|
|
133
|
+
const lessonMode = configurableMemoryService.getSetting?.("error_lesson_mode", "all") ??
|
|
134
|
+
"all";
|
|
135
|
+
const verdict = classify({
|
|
136
|
+
type: "error",
|
|
137
|
+
content: lesson,
|
|
138
|
+
metadata: { dispatch: { code: dispatched.code, hint: dispatched.hint } },
|
|
139
|
+
});
|
|
140
|
+
if (lessonMode === "triage_only" && verdict === "inferable") {
|
|
141
|
+
this.metrics?.incr("error_lessons_suppressed", 1);
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
// v0.3.0 (K3-018): optional LLM enrichment opt-in. Runs only when
|
|
145
|
+
// the throttle check passed (bug #5).
|
|
146
|
+
let enrichedLesson = lesson;
|
|
147
|
+
try {
|
|
148
|
+
const enrichment = await this.enrichFn(lesson, redactedStderr, redactedStdout);
|
|
149
|
+
if (enrichment) {
|
|
150
|
+
enrichedLesson = `${lesson}\n${enrichment}`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// enrichment failures are non-blocking
|
|
155
|
+
}
|
|
156
|
+
const metadata = {};
|
|
157
|
+
if (input.callID) {
|
|
158
|
+
metadata.origin_call_id = input.callID;
|
|
159
|
+
}
|
|
160
|
+
// v0.4.0 (K4-005) — persist the dispatch result so injection and
|
|
161
|
+
// promotion can reuse the rescued errorType and hint without
|
|
162
|
+
// re-dispatching. Stored even when no code matched ({code:null,hint:null})
|
|
163
|
+
// so callers can distinguish "checked and unmatched" from "absent".
|
|
164
|
+
metadata.dispatch = { code: dispatched.code, hint: dispatched.hint };
|
|
165
|
+
let finalContent;
|
|
166
|
+
if (sourceOutput.length > 0) {
|
|
167
|
+
const fullLen = enrichedLesson.length + CONTEXT_PREFIX.length + sourceOutput.length;
|
|
168
|
+
if (fullLen <= MAX_CONTENT_CHARS) {
|
|
169
|
+
finalContent = `${enrichedLesson}${CONTEXT_PREFIX}${sourceOutput}`;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const budget = MAX_CONTENT_CHARS -
|
|
173
|
+
enrichedLesson.length -
|
|
174
|
+
CONTEXT_PREFIX.length -
|
|
175
|
+
TRUNC_SUFFIX.length;
|
|
176
|
+
const truncated = sourceOutput.slice(0, Math.max(0, budget));
|
|
177
|
+
finalContent = `${enrichedLesson}${CONTEXT_PREFIX}${truncated}${TRUNC_SUFFIX}`;
|
|
178
|
+
metadata.truncated = true;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
finalContent = enrichedLesson;
|
|
183
|
+
}
|
|
184
|
+
// K2-007: the per-fingerprint throttle check ran above (before
|
|
185
|
+
// enrichment); `fp` is reused here so dedup/throttle and the saved
|
|
186
|
+
// memory agree on identity.
|
|
187
|
+
const id = this.memoryService.save({
|
|
188
|
+
type: "error",
|
|
189
|
+
content: finalContent,
|
|
190
|
+
scope: "project",
|
|
191
|
+
sourceTool: input.toolName,
|
|
192
|
+
sourceSession: input.sessionId,
|
|
193
|
+
metadata,
|
|
194
|
+
origin: "reflector",
|
|
195
|
+
projectId: projectId ?? undefined,
|
|
196
|
+
fingerprint: fp,
|
|
197
|
+
});
|
|
198
|
+
return id;
|
|
199
|
+
}
|
|
200
|
+
generateHeuristicLesson(input) {
|
|
201
|
+
const dispatched = input.dispatched ??
|
|
202
|
+
this.dispatchLesson(input.firstErrorLine, "", input.errorType);
|
|
203
|
+
const suggestion = SUGGESTIONS[input.errorType] ?? SUGGESTIONS.unknown;
|
|
204
|
+
const shownErrorType = input.displayErrorType ?? input.errorType;
|
|
205
|
+
const line = input.firstErrorLine.length > MAX_ERROR_LINE_CHARS
|
|
206
|
+
? `${input.firstErrorLine.slice(0, MAX_ERROR_LINE_CHARS)}...`
|
|
207
|
+
: input.firstErrorLine;
|
|
208
|
+
let lesson = `When ${input.toolName} fails with ${shownErrorType}: ${line}\nSuggestion: ${suggestion}`;
|
|
209
|
+
if (dispatched.code && dispatched.hint) {
|
|
210
|
+
lesson += `\nLikely cause: ${dispatched.hint} (code ${dispatched.code})`;
|
|
211
|
+
}
|
|
212
|
+
return lesson;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* v0.2.0 (K2-018 / D2-09) — deterministic per-error-code rule dispatch.
|
|
216
|
+
* Pure regex sweep over `stderr + '\n' + stdout` with NO LLM hop. Returns
|
|
217
|
+
* a short stable `code` + `hint` pair when a known rule matches, or
|
|
218
|
+
* `{ code: null, hint: null }` for the v0.1.x fallback path (which keeps
|
|
219
|
+
* the v0.1.x `SUGGESTIONS[errorType]` suggestion verbatim).
|
|
220
|
+
*
|
|
221
|
+
* Exported for unit testing (K2-019).
|
|
222
|
+
*/
|
|
223
|
+
dispatchLesson(stderr, stdout, errorType) {
|
|
224
|
+
const combined = `${stderr}\n${stdout}`;
|
|
225
|
+
const tsMatch = combined.match(TS_CODE_RE);
|
|
226
|
+
if (tsMatch) {
|
|
227
|
+
const num = tsMatch[1];
|
|
228
|
+
const hint = TS_CODE_RULES[num] ?? `review TS${num}`;
|
|
229
|
+
return { code: `TS${num}`, hint };
|
|
230
|
+
}
|
|
231
|
+
const pyMatch = combined.match(PY_LINT_RE);
|
|
232
|
+
if (pyMatch) {
|
|
233
|
+
const rule = pyMatch[1] || pyMatch[2] || pyMatch[3] || "unknown";
|
|
234
|
+
return { code: rule, hint: `review python lint: ${rule}` };
|
|
235
|
+
}
|
|
236
|
+
const rustMatch = combined.match(RUST_CODE_RE);
|
|
237
|
+
if (rustMatch) {
|
|
238
|
+
const code = rustMatch[1];
|
|
239
|
+
return {
|
|
240
|
+
code,
|
|
241
|
+
hint: "add the dependency to Cargo.toml or use a full path (crate::...)",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const sysMatch = combined.match(SYSCALL_RE);
|
|
245
|
+
if (sysMatch) {
|
|
246
|
+
const code = sysMatch[1];
|
|
247
|
+
const hint = code === "EADDRINUSE"
|
|
248
|
+
? "free the port (netstat -ano | findstr :PORT) or change the port"
|
|
249
|
+
: `review syscall: ${code}`;
|
|
250
|
+
return { code, hint };
|
|
251
|
+
}
|
|
252
|
+
const cmdNotFoundMatch = combined.match(COMMAND_NOT_FOUND_RE);
|
|
253
|
+
if (cmdNotFoundMatch) {
|
|
254
|
+
const cmd = cmdNotFoundMatch[1];
|
|
255
|
+
return {
|
|
256
|
+
code: cmd,
|
|
257
|
+
hint: `install the tool (e.g. npm i -g ${cmd}) or call it by its full path`,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
const errMatch = combined.match(GENERIC_ERROR_RE);
|
|
261
|
+
if (errMatch) {
|
|
262
|
+
const name = errMatch[1];
|
|
263
|
+
return { code: name, hint: `review error class: ${name}` };
|
|
264
|
+
}
|
|
265
|
+
const cmdMatch = combined.match(COMMAND_FAILED_RE);
|
|
266
|
+
if (cmdMatch) {
|
|
267
|
+
const cmd = cmdMatch[1];
|
|
268
|
+
return { code: cmd, hint: `review failing command: ${cmd}` };
|
|
269
|
+
}
|
|
270
|
+
return { code: null, hint: null };
|
|
271
|
+
}
|
|
272
|
+
redactPaths(text) {
|
|
273
|
+
return redactPathsText(text);
|
|
274
|
+
}
|
|
275
|
+
redactSecrets(text) {
|
|
276
|
+
let out = text;
|
|
277
|
+
for (const pat of SECRET_PATTERNS) {
|
|
278
|
+
out = out.replace(pat, (match) => {
|
|
279
|
+
// BUG-013 — preserve the original separator so
|
|
280
|
+
// `auth_token = abc123` keeps its spacing: only the
|
|
281
|
+
// value is replaced.
|
|
282
|
+
const m = match.match(/^(.+?)(\s*[=:]\s*)(\S+)$/);
|
|
283
|
+
if (m) {
|
|
284
|
+
return `${m[1]}${m[2]}<redacted>`;
|
|
285
|
+
}
|
|
286
|
+
const parts = match.split(/\s+/);
|
|
287
|
+
return `${parts[0]} <redacted>`;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
extractFirstErrorLine(text) {
|
|
293
|
+
const lines = text.split(/\r?\n/);
|
|
294
|
+
for (const line of lines) {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (trimmed.length > 0 && ERROR_LINE_RE.test(trimmed)) {
|
|
297
|
+
return trimmed;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
for (const line of lines) {
|
|
301
|
+
const trimmed = line.trim();
|
|
302
|
+
if (trimmed.length > 0)
|
|
303
|
+
return trimmed;
|
|
304
|
+
}
|
|
305
|
+
return "";
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { ArtifactWriter } from "./ArtifactWriter.js";
|
|
2
|
+
import type { HostSurface } from "./host.js";
|
|
3
|
+
/**
|
|
4
|
+
* The four sources `resolve()` tries, in order (plan §5.2, D9-13).
|
|
5
|
+
*/
|
|
6
|
+
export type IdentitySource = "declared" | "remote" | "host" | "path";
|
|
7
|
+
/**
|
|
8
|
+
* The outcome of resolving a repository identity.
|
|
9
|
+
*/
|
|
10
|
+
export interface ResolvedIdentity {
|
|
11
|
+
/** The repository-scoped id every Team scope is keyed on. */
|
|
12
|
+
repoId: string;
|
|
13
|
+
/** Which source produced `repoId`. */
|
|
14
|
+
source: IdentitySource;
|
|
15
|
+
/**
|
|
16
|
+
* A short, non-secret description of how the id was derived,
|
|
17
|
+
* surfaced by `kevin_project show`. Never contains a credential
|
|
18
|
+
* or an absolute path.
|
|
19
|
+
*/
|
|
20
|
+
evidence: string;
|
|
21
|
+
/** The v0.7.0 project scope, always returned regardless of source. */
|
|
22
|
+
projectId: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read the first `url = …` value inside `[remote "<name>"]` of a
|
|
26
|
+
* `.git/config`-style INI text.
|
|
27
|
+
*
|
|
28
|
+
* Line-oriented reader, not an INI library: tracks the current
|
|
29
|
+
* `[section "sub"]` header, tolerates tabs, spaces around `=`,
|
|
30
|
+
* CRLF, and comment lines. Returns `null` on anything unrecognised
|
|
31
|
+
* and never throws — on any input, including binary.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseGitConfigRemote(text: string, name?: string): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Fold the URL shapes git accepts into one canonical string.
|
|
36
|
+
*
|
|
37
|
+
* Steps, in order (K8-005):
|
|
38
|
+
* 1. strip a trailing `.git`;
|
|
39
|
+
* 2. strip a `scheme://` prefix;
|
|
40
|
+
* 3. strip everything up to and including the last `@` in the
|
|
41
|
+
* authority (userinfo and embedded credentials — a token must
|
|
42
|
+
* never reach a hash that lands in a committed file, D8-04);
|
|
43
|
+
* 4. strip a numeric port (`:8443`, `:443`, …) so the same repo
|
|
44
|
+
* served over different transports folds to one id;
|
|
45
|
+
* 5. rewrite the scp-style `host:path` separator to `host/path`;
|
|
46
|
+
* 6. strip a trailing `/`;
|
|
47
|
+
* 7. lowercase the whole result.
|
|
48
|
+
*
|
|
49
|
+
* Returns `null` when the result contains no `/` (e.g. a local
|
|
50
|
+
* path remote) — the caller falls through to the path source.
|
|
51
|
+
*/
|
|
52
|
+
export declare function normalizeRemote(url: string): string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Derive a repository id from a normalized remote (K8-006).
|
|
55
|
+
*
|
|
56
|
+
* The domain prefix means a repo id can never collide with a memory
|
|
57
|
+
* fingerprint computed over the same string, and it leaves room to
|
|
58
|
+
* version the derivation later.
|
|
59
|
+
*/
|
|
60
|
+
export declare function computeRepoId(normalized: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the repository identity for `cwd`, trying the four
|
|
63
|
+
* sources in order (plan §5.2, D9-13):
|
|
64
|
+
*
|
|
65
|
+
* 1. `declared` — `.kevin/project.json` → `id` (validated as
|
|
66
|
+
* exactly 16 lowercase hex characters; anything else is ignored
|
|
67
|
+
* and falls through, because a hand-edited garbage id in a
|
|
68
|
+
* committed file would otherwise scope a whole team's corpus
|
|
69
|
+
* onto a typo);
|
|
70
|
+
* 2. `remote` — `.git/config` → `[remote "origin"] url`,
|
|
71
|
+
* normalized and hashed;
|
|
72
|
+
* 3. `host` — the value the host resolved for this directory,
|
|
73
|
+
* `host.project.worktree` first, `host.project.directory` as
|
|
74
|
+
* fallback; both empty or absent falls through to `path`.
|
|
75
|
+
* This is strictly better than cwd — the host's own
|
|
76
|
+
* ToolContext documents "prefer this over cwd" —
|
|
77
|
+
* but it sits below the explicit sources, because monorepos and
|
|
78
|
+
* D8-03's confirmed re-keying depend on `.kevin/project.json`
|
|
79
|
+
* winning;
|
|
80
|
+
* 4. `path` — `fingerprint(cwd)`, the v0.7.0 behaviour, preserved
|
|
81
|
+
* exactly when `host` is absent.
|
|
82
|
+
*
|
|
83
|
+
* Never throws: a directory that is not a git repository, an
|
|
84
|
+
* unreadable `.git/config`, a malformed `project.json`, and a host
|
|
85
|
+
* with no usable project fields all fall through to source 4.
|
|
86
|
+
* `projectId` is always returned alongside `repoId`, regardless of
|
|
87
|
+
* which source won.
|
|
88
|
+
*/
|
|
89
|
+
export declare function resolve(cwd: string, host?: HostSurface): ResolvedIdentity;
|
|
90
|
+
/**
|
|
91
|
+
* The outcome of `initProjectFile` (K8-008).
|
|
92
|
+
*/
|
|
93
|
+
export interface InitProjectResult {
|
|
94
|
+
/** Whether the file was written. */
|
|
95
|
+
ok: boolean;
|
|
96
|
+
/** The absolute path the file was written to, or refused. */
|
|
97
|
+
path: string;
|
|
98
|
+
/** Human-readable refusal reason when `ok` is false. */
|
|
99
|
+
reason?: string;
|
|
100
|
+
/** The pinned id when the file was written. */
|
|
101
|
+
id?: string;
|
|
102
|
+
/** The ISO-8601 Z timestamp written into the file. */
|
|
103
|
+
createdAt?: string;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Write `.kevin/project.json` pinning the repository identity (K8-008
|
|
107
|
+
* / plan §5.8). The id comes from the current `resolve(cwd)` — so `init`
|
|
108
|
+
* in a repository with a remote *pins* the remote-derived id, which is
|
|
109
|
+
* the point: it survives the organisation renaming the repo (a remote
|
|
110
|
+
* URL change would otherwise silently re-scope the corpus).
|
|
111
|
+
*
|
|
112
|
+
* The file is `{"id": "<16 hex>", "created_at": "<ISO-8601 Z>",
|
|
113
|
+
* "generator": "opencode-kevin/0.8.0"}` with sorted keys and a
|
|
114
|
+
* terminating newline (deterministic bytes, the codec discipline of
|
|
115
|
+
* plan §7.3). `init` refuses when the file already exists: overwriting
|
|
116
|
+
* it re-scopes a team's corpus, and that is `rekey`'s job (K8-009),
|
|
117
|
+
* with a confirmation.
|
|
118
|
+
*
|
|
119
|
+
* NOTE (K8-019): the write goes through `ArtifactWriter` in whole-file
|
|
120
|
+
* mode — `.kevin/project.json` is a Kevin-owned file, and D8-08 leaves
|
|
121
|
+
* no scenario in which a second raw write path is acceptable (asserted
|
|
122
|
+
* by tests/unit/single_write_path.test.ts).
|
|
123
|
+
*/
|
|
124
|
+
export declare function initProjectFile(cwd: string, writer: ArtifactWriter): InitProjectResult;
|