zelari-code 0.1.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/LICENSE +21 -0
- package/README.md +156 -0
- package/bin/zelari-code.js +64 -0
- package/dist/agents/advancedTools.js +115 -0
- package/dist/agents/advancedTools.js.map +1 -0
- package/dist/agents/councilApi.js +375 -0
- package/dist/agents/councilApi.js.map +1 -0
- package/dist/agents/councilDirectives.js +100 -0
- package/dist/agents/councilDirectives.js.map +1 -0
- package/dist/agents/promoteMember.js +210 -0
- package/dist/agents/promoteMember.js.map +1 -0
- package/dist/agents/promptModules.js +139 -0
- package/dist/agents/promptModules.js.map +1 -0
- package/dist/agents/roles.js +281 -0
- package/dist/agents/roles.js.map +1 -0
- package/dist/agents/skills/builtin/debugging.js +248 -0
- package/dist/agents/skills/builtin/debugging.js.map +1 -0
- package/dist/agents/skills/builtin/docs.js +301 -0
- package/dist/agents/skills/builtin/docs.js.map +1 -0
- package/dist/agents/skills/builtin/git-ops.js +301 -0
- package/dist/agents/skills/builtin/git-ops.js.map +1 -0
- package/dist/agents/skills/builtin/planning.js +335 -0
- package/dist/agents/skills/builtin/planning.js.map +1 -0
- package/dist/agents/skills/builtin/refactoring.js +261 -0
- package/dist/agents/skills/builtin/refactoring.js.map +1 -0
- package/dist/agents/skills/builtin/review.js +302 -0
- package/dist/agents/skills/builtin/review.js.map +1 -0
- package/dist/agents/skills/builtin/testing.js +245 -0
- package/dist/agents/skills/builtin/testing.js.map +1 -0
- package/dist/agents/skills.js +272 -0
- package/dist/agents/skills.js.map +1 -0
- package/dist/agents/systemPromptBuilder.js +151 -0
- package/dist/agents/systemPromptBuilder.js.map +1 -0
- package/dist/agents/toolSchemas.js +249 -0
- package/dist/agents/toolSchemas.js.map +1 -0
- package/dist/agents/tools.js +196 -0
- package/dist/agents/tools.js.map +1 -0
- package/dist/agents/vaultTools.js +134 -0
- package/dist/agents/vaultTools.js.map +1 -0
- package/dist/cli/app.js +1823 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/cli/branchManager.js +280 -0
- package/dist/cli/branchManager.js.map +1 -0
- package/dist/cli/compaction.js +63 -0
- package/dist/cli/compaction.js.map +1 -0
- package/dist/cli/components/ChatStream.js +23 -0
- package/dist/cli/components/ChatStream.js.map +1 -0
- package/dist/cli/components/CollapsibleToolOutput.js +38 -0
- package/dist/cli/components/CollapsibleToolOutput.js.map +1 -0
- package/dist/cli/components/Header.js +31 -0
- package/dist/cli/components/Header.js.map +1 -0
- package/dist/cli/components/InputBar.js +9 -0
- package/dist/cli/components/InputBar.js.map +1 -0
- package/dist/cli/components/Sidebar.js +17 -0
- package/dist/cli/components/Sidebar.js.map +1 -0
- package/dist/cli/councilCost.js +81 -0
- package/dist/cli/councilCost.js.map +1 -0
- package/dist/cli/councilDispatcher.js +56 -0
- package/dist/cli/councilDispatcher.js.map +1 -0
- package/dist/cli/councilFeedback.js +164 -0
- package/dist/cli/councilFeedback.js.map +1 -0
- package/dist/cli/crossProviderFailover.js +83 -0
- package/dist/cli/crossProviderFailover.js.map +1 -0
- package/dist/cli/gitOps.js +96 -0
- package/dist/cli/gitOps.js.map +1 -0
- package/dist/cli/grokOAuth.js +375 -0
- package/dist/cli/grokOAuth.js.map +1 -0
- package/dist/cli/keyStore.js +263 -0
- package/dist/cli/keyStore.js.map +1 -0
- package/dist/cli/keyValidator.js +83 -0
- package/dist/cli/keyValidator.js.map +1 -0
- package/dist/cli/main.bundled.js +21428 -0
- package/dist/cli/main.bundled.js.map +7 -0
- package/dist/cli/main.js +72 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/metrics.js +131 -0
- package/dist/cli/metrics.js.map +1 -0
- package/dist/cli/modelDiscovery.js +234 -0
- package/dist/cli/modelDiscovery.js.map +1 -0
- package/dist/cli/modelPricing.js +112 -0
- package/dist/cli/modelPricing.js.map +1 -0
- package/dist/cli/oauthCallbackServer.js +207 -0
- package/dist/cli/oauthCallbackServer.js.map +1 -0
- package/dist/cli/provider/openai-compatible.js +236 -0
- package/dist/cli/provider/openai-compatible.js.map +1 -0
- package/dist/cli/providerConfig.js +210 -0
- package/dist/cli/providerConfig.js.map +1 -0
- package/dist/cli/providerFailover.js +90 -0
- package/dist/cli/providerFailover.js.map +1 -0
- package/dist/cli/refreshRegistry.js +77 -0
- package/dist/cli/refreshRegistry.js.map +1 -0
- package/dist/cli/safety/auditLogger.js +111 -0
- package/dist/cli/safety/auditLogger.js.map +1 -0
- package/dist/cli/safety/sandboxPath.js +62 -0
- package/dist/cli/safety/sandboxPath.js.map +1 -0
- package/dist/cli/safety/shellBlocklist.js +71 -0
- package/dist/cli/safety/shellBlocklist.js.map +1 -0
- package/dist/cli/sessionManager.js +149 -0
- package/dist/cli/sessionManager.js.map +1 -0
- package/dist/cli/skillCache.js +205 -0
- package/dist/cli/skillCache.js.map +1 -0
- package/dist/cli/skillHistory.js +178 -0
- package/dist/cli/skillHistory.js.map +1 -0
- package/dist/cli/skillSuggest.js +132 -0
- package/dist/cli/skillSuggest.js.map +1 -0
- package/dist/cli/slashCommands.js +431 -0
- package/dist/cli/slashCommands.js.map +1 -0
- package/dist/cli/toolRegistry.js +187 -0
- package/dist/cli/toolRegistry.js.map +1 -0
- package/dist/cli/updater.js +157 -0
- package/dist/cli/updater.js.map +1 -0
- package/dist/main/core/AgentHarness.js +345 -0
- package/dist/main/core/AgentHarness.js.map +1 -0
- package/dist/main/core/providerStream.js +99 -0
- package/dist/main/core/providerStream.js.map +1 -0
- package/dist/main/core/sessionJsonl.js +99 -0
- package/dist/main/core/sessionJsonl.js.map +1 -0
- package/dist/main/core/tools/auditLog.js +26 -0
- package/dist/main/core/tools/auditLog.js.map +1 -0
- package/dist/main/core/tools/builtin/filesystem.js +110 -0
- package/dist/main/core/tools/builtin/filesystem.js.map +1 -0
- package/dist/main/core/tools/builtin/search.js +56 -0
- package/dist/main/core/tools/builtin/search.js.map +1 -0
- package/dist/main/core/tools/builtin/shell.js +51 -0
- package/dist/main/core/tools/builtin/shell.js.map +1 -0
- package/dist/main/core/tools/registry.js +71 -0
- package/dist/main/core/tools/registry.js.map +1 -0
- package/dist/main/core/tools/toolTypes.js +8 -0
- package/dist/main/core/tools/toolTypes.js.map +1 -0
- package/dist/main/core/tools/zodBridge.js +48 -0
- package/dist/main/core/tools/zodBridge.js.map +1 -0
- package/dist/shared/eventBus.js +104 -0
- package/dist/shared/eventBus.js.map +1 -0
- package/dist/shared/events.js +73 -0
- package/dist/shared/events.js.map +1 -0
- package/dist/shared/index.js +8 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/types/context.js +3 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/knowledge.js +2 -0
- package/dist/types/knowledge.js.map +1 -0
- package/dist/types/systemTypes.js +10 -0
- package/dist/types/systemTypes.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
/**
|
|
4
|
+
* Sidebar showing: formatted skill list (from formatSkillList), session count,
|
|
5
|
+
* and an indicator when the user is typing a slash command.
|
|
6
|
+
*/
|
|
7
|
+
export function Sidebar({ skillList, sessionCount, isSlashMode }) {
|
|
8
|
+
return (React.createElement(Box, { flexDirection: "column", width: 40, borderStyle: "single", borderColor: "gray", paddingX: 1 },
|
|
9
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Skills & sessions"),
|
|
10
|
+
React.createElement(Text, { dimColor: true },
|
|
11
|
+
"Sessions: ",
|
|
12
|
+
sessionCount),
|
|
13
|
+
isSlashMode && React.createElement(Text, { color: "yellow" }, "\u2328 slash command mode"),
|
|
14
|
+
React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
15
|
+
React.createElement(Text, null, skillList))));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=Sidebar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Sidebar.js","sourceRoot":"","sources":["../../../src/cli/components/Sidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAQhC;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAgB;IAC5E,OAAO,CACL,oBAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,KAAK,EAAE,EAAE,EAAE,WAAW,EAAC,QAAQ,EAAC,WAAW,EAAC,MAAM,EAAC,QAAQ,EAAE,CAAC;QACxF,oBAAC,IAAI,IAAC,IAAI,QAAC,KAAK,EAAC,MAAM,wBAA6B;QACpD,oBAAC,IAAI,IAAC,QAAQ;;YAAY,YAAY,CAAQ;QAC7C,WAAW,IAAI,oBAAC,IAAI,IAAC,KAAK,EAAC,QAAQ,gCAA4B;QAChE,oBAAC,GAAG,IAAC,SAAS,EAAE,CAAC,EAAE,aAAa,EAAC,QAAQ;YACvC,oBAAC,IAAI,QAAE,SAAS,CAAQ,CACpB,CACF,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* councilCost — per-member cost tracking for council runs (Task I.1, v3-I).
|
|
3
|
+
*
|
|
4
|
+
* Accumulates `MemberCost` records as each member of the council completes.
|
|
5
|
+
* Side-effect free and deterministic so it can be unit-tested without
|
|
6
|
+
* filesystem or network. The actual persistence / event emission is the
|
|
7
|
+
* caller's job (see `runCouncilPure` in `councilApi.ts` which feeds this
|
|
8
|
+
* helper and emits `member_cost` BrainEvents downstream).
|
|
9
|
+
*
|
|
10
|
+
* Cost fields captured per member:
|
|
11
|
+
* - promptTokens / completionTokens / totalTokens
|
|
12
|
+
* - durationMs (wall-clock)
|
|
13
|
+
* - toolCalls (count of tool_execution_start events)
|
|
14
|
+
* - errored (boolean)
|
|
15
|
+
*
|
|
16
|
+
* @see docs/plans/2026-06-29-anathema-coder-v3-I.md (I.1)
|
|
17
|
+
*/
|
|
18
|
+
export class MemberCostTracker {
|
|
19
|
+
costs = new Map();
|
|
20
|
+
now;
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.now = options.now ?? Date.now;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Record cost for one member. If the same memberId is recorded twice
|
|
26
|
+
* (shouldn't happen in practice — council members are unique), the
|
|
27
|
+
* second call REPLACES the first. This makes accidental double-count
|
|
28
|
+
* visible: the caller sees the latest numbers rather than a silent sum.
|
|
29
|
+
*/
|
|
30
|
+
record(input) {
|
|
31
|
+
const usage = input.usage ?? null;
|
|
32
|
+
const prompt = usage?.promptTokens ?? 0;
|
|
33
|
+
const completion = usage?.completionTokens ?? 0;
|
|
34
|
+
const cost = {
|
|
35
|
+
memberId: input.memberId,
|
|
36
|
+
name: input.name,
|
|
37
|
+
promptTokens: prompt,
|
|
38
|
+
completionTokens: completion,
|
|
39
|
+
totalTokens: usage?.totalTokens ?? prompt + completion,
|
|
40
|
+
durationMs: Math.max(0, Math.round(input.durationMs)),
|
|
41
|
+
toolCalls: input.toolCalls ?? 0,
|
|
42
|
+
errored: input.errored ?? false,
|
|
43
|
+
};
|
|
44
|
+
this.costs.set(input.memberId, cost);
|
|
45
|
+
return cost;
|
|
46
|
+
}
|
|
47
|
+
/** Snapshot of all recorded costs, in insertion order. */
|
|
48
|
+
finalize() {
|
|
49
|
+
return Array.from(this.costs.values());
|
|
50
|
+
}
|
|
51
|
+
/** Total tokens across all members (prompt + completion). */
|
|
52
|
+
totalTokens() {
|
|
53
|
+
let t = 0;
|
|
54
|
+
for (const c of this.costs.values())
|
|
55
|
+
t += c.totalTokens;
|
|
56
|
+
return t;
|
|
57
|
+
}
|
|
58
|
+
/** Total wall-clock time across all members. Note: this is sum-of-mems,
|
|
59
|
+
* not elapsed council time (specialists run sequentially). */
|
|
60
|
+
totalDurationMs() {
|
|
61
|
+
let t = 0;
|
|
62
|
+
for (const c of this.costs.values())
|
|
63
|
+
t += c.durationMs;
|
|
64
|
+
return t;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* JSON-serializable snapshot of the tracker state, including a generated
|
|
68
|
+
* `ts` field. Useful for persistence and event payloads.
|
|
69
|
+
*/
|
|
70
|
+
toJSON() {
|
|
71
|
+
return { ts: this.now(), costs: this.finalize() };
|
|
72
|
+
}
|
|
73
|
+
/** Reconstruct a tracker from a JSON snapshot. Used by tests + persistence. */
|
|
74
|
+
static fromJSON(json, options = {}) {
|
|
75
|
+
const t = new MemberCostTracker(options);
|
|
76
|
+
for (const c of json.costs)
|
|
77
|
+
t.costs.set(c.memberId, c);
|
|
78
|
+
return t;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=councilCost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"councilCost.js","sourceRoot":"","sources":["../../src/cli/councilCost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAqCH,MAAM,OAAO,iBAAiB;IACX,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IACtC,GAAG,CAAe;IAEnC,YAAY,UAAoC,EAAE;QAChD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAkB;QACvB,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC;QAClC,MAAM,MAAM,GAAG,KAAK,EAAE,YAAY,IAAI,CAAC,CAAC;QACxC,MAAM,UAAU,GAAG,KAAK,EAAE,gBAAgB,IAAI,CAAC,CAAC;QAChD,MAAM,IAAI,GAAe;YACvB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,YAAY,EAAE,MAAM;YACpB,gBAAgB,EAAE,UAAU;YAC5B,WAAW,EAAE,KAAK,EAAE,WAAW,IAAI,MAAM,GAAG,UAAU;YACtD,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACrD,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,CAAC;YAC/B,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,KAAK;SAChC,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,0DAA0D;IAC1D,QAAQ;QACN,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,6DAA6D;IAC7D,WAAW;QACT,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,CAAC,IAAI,CAAC,CAAC,WAAW,CAAC;QACxD,OAAO,CAAC,CAAC;IACX,CAAC;IAED;mEAC+D;IAC/D,eAAe;QACb,IAAI,CAAC,GAAG,CAAC,CAAC;QACV,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAAE,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC;QACvD,OAAO,CAAC,CAAC;IACX,CAAC;IAED;;;OAGG;IACH,MAAM;QACJ,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;IACpD,CAAC;IAED,+EAA+E;IAC/E,MAAM,CAAC,QAAQ,CACb,IAA0C,EAC1C,UAAoC,EAAE;QAEtC,MAAM,CAAC,GAAG,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK;YAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,CAAC;IACX,CAAC;CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* councilDispatcher — CLI-side bridge to runCouncilPure.
|
|
3
|
+
*
|
|
4
|
+
* Builds a minimal {@link PureCouncilConfig} from CLI env (apiKey, model,
|
|
5
|
+
* provider), wires the openai-compatible provider stream, and yields the
|
|
6
|
+
* council run as an AsyncIterable<BrainEvent>. The CLI app then renders
|
|
7
|
+
* events to the chat and persists them via SessionJsonlWriter.
|
|
8
|
+
*
|
|
9
|
+
* Default config:
|
|
10
|
+
* - councilSize: 3 (sisyphus + prometheus + hephaestus — no oracle)
|
|
11
|
+
* - debateMode: false (no oracle-debate loop)
|
|
12
|
+
* - ragContext / workspaceContext: empty (CLI doesn't have a vault here)
|
|
13
|
+
*
|
|
14
|
+
* Phase 14.7: multi-agent council invocation from the Ink CLI.
|
|
15
|
+
*
|
|
16
|
+
* @see docs/plans/2026-06-28-zelari-code.md (Task 14.7)
|
|
17
|
+
*/
|
|
18
|
+
import { runCouncilPure, } from '../agents/councilApi.js';
|
|
19
|
+
export class CouncilDispatchError extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'CouncilDispatchError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run a council on `userMessage` and yield the resulting BrainEvents.
|
|
27
|
+
*
|
|
28
|
+
* Throws CouncilDispatchError synchronously if apiKey is missing.
|
|
29
|
+
* The returned async iterable emits the full council lifecycle; consumers
|
|
30
|
+
* should iterate to completion or break early to cancel.
|
|
31
|
+
*/
|
|
32
|
+
export async function* dispatchCouncil(userMessage, options) {
|
|
33
|
+
if (!options.apiKey || options.apiKey.trim().length === 0) {
|
|
34
|
+
throw new CouncilDispatchError('Missing apiKey — set OPENAI_API_KEY (or pass it via env) before invoking /council.');
|
|
35
|
+
}
|
|
36
|
+
if (!userMessage || userMessage.trim().length === 0) {
|
|
37
|
+
throw new CouncilDispatchError('Empty userMessage — /council requires a non-empty input.');
|
|
38
|
+
}
|
|
39
|
+
const config = {
|
|
40
|
+
apiKey: options.apiKey,
|
|
41
|
+
provider: options.provider ?? 'openai-compatible',
|
|
42
|
+
model: options.model,
|
|
43
|
+
councilSize: options.councilSize ?? 3,
|
|
44
|
+
debateMode: options.debateMode ?? false,
|
|
45
|
+
ragContext: options.ragContext ?? '',
|
|
46
|
+
workspaceContext: options.workspaceContext ?? '',
|
|
47
|
+
providerStream: options.providerStream,
|
|
48
|
+
eventBus: options.eventBus,
|
|
49
|
+
sessionId: options.sessionId,
|
|
50
|
+
tools: options.tools,
|
|
51
|
+
maxToolCallsPerTurn: options.maxToolCallsPerTurn,
|
|
52
|
+
feedbackStore: options.feedbackStore,
|
|
53
|
+
};
|
|
54
|
+
yield* runCouncilPure(userMessage, config, {});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=councilDispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"councilDispatcher.js","sourceRoot":"","sources":["../../src/cli/councilDispatcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,cAAc,GAEf,MAAM,yBAAyB,CAAC;AAkDjC,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,eAAe,CACpC,WAAmB,EACnB,OAA+B;IAE/B,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,MAAM,IAAI,oBAAoB,CAC5B,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,WAAW,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,oBAAoB,CAAC,0DAA0D,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,MAAM,GAAsB;QAChC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,mBAAmB;QACjD,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,CAAC;QACrC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,KAAK;QACvC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE;QACpC,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,EAAE;QAChD,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,mBAAmB,EAAE,OAAO,CAAC,mBAAmB;QAChD,aAAa,EAAE,OAAO,CAAC,aAAa;KACrC,CAAC;IAEF,KAAK,CAAC,CAAC,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* councilFeedback — persistent feedback store for council members (Task I.2, v3-I).
|
|
3
|
+
*
|
|
4
|
+
* Stores user ratings (1-5) per member across sessions in a JSON file under
|
|
5
|
+
* the user's home directory. Used by `/council-feedback <memberId> <1-5> [note]`
|
|
6
|
+
* to register feedback, and (optionally) by `runCouncilPure` to rank members
|
|
7
|
+
* by historical score.
|
|
8
|
+
*
|
|
9
|
+
* Pure node:fs — no Electron deps, browser-importable for jsdom tests.
|
|
10
|
+
* Env override: `ANATHEMA_COUNCIL_FEEDBACK_FILE` (useful for tests + CI).
|
|
11
|
+
*
|
|
12
|
+
* @see docs/plans/2026-06-29-anathema-coder-v3-I.md (I.2)
|
|
13
|
+
*/
|
|
14
|
+
import { promises as fs, existsSync, readFileSync, writeFileSync, mkdirSync, } from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import os from 'node:os';
|
|
17
|
+
export class FeedbackStore {
|
|
18
|
+
file;
|
|
19
|
+
now;
|
|
20
|
+
entries = [];
|
|
21
|
+
constructor(options = {}) {
|
|
22
|
+
this.file = options.file
|
|
23
|
+
?? (process.env.ANATHEMA_COUNCIL_FEEDBACK_FILE
|
|
24
|
+
?? path.join(os.homedir(), '.tmp', 'zelari-code', 'council-feedback.json'));
|
|
25
|
+
this.now = options.now ?? Date.now;
|
|
26
|
+
this.load();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Record a feedback entry. Validates score in [1,5]. Throws on invalid
|
|
30
|
+
* input — caller's job to validate user input first.
|
|
31
|
+
*/
|
|
32
|
+
record(input) {
|
|
33
|
+
if (!input.memberId || input.memberId.trim().length === 0) {
|
|
34
|
+
throw new Error('FeedbackStore.record: memberId is required');
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isInteger(input.score) || input.score < 1 || input.score > 5) {
|
|
37
|
+
throw new Error(`FeedbackStore.record: score must be an integer in [1,5], got ${input.score}`);
|
|
38
|
+
}
|
|
39
|
+
const entry = {
|
|
40
|
+
ts: input.ts ?? this.now(),
|
|
41
|
+
memberId: input.memberId.trim(),
|
|
42
|
+
score: input.score,
|
|
43
|
+
...(input.note ? { note: input.note } : {}),
|
|
44
|
+
...(input.sessionId ? { sessionId: input.sessionId } : {}),
|
|
45
|
+
};
|
|
46
|
+
this.entries.push(entry);
|
|
47
|
+
this.save();
|
|
48
|
+
return entry;
|
|
49
|
+
}
|
|
50
|
+
/** Aggregate stats for one member. Returns defaults (0/0/0) when unknown. */
|
|
51
|
+
getStats(memberId) {
|
|
52
|
+
const filtered = this.entries.filter((e) => e.memberId === memberId);
|
|
53
|
+
if (filtered.length === 0) {
|
|
54
|
+
return { memberId, count: 0, avg: 0, lastTs: 0 };
|
|
55
|
+
}
|
|
56
|
+
let sum = 0;
|
|
57
|
+
let lastTs = 0;
|
|
58
|
+
for (const e of filtered) {
|
|
59
|
+
sum += e.score;
|
|
60
|
+
if (e.ts > lastTs)
|
|
61
|
+
lastTs = e.ts;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
memberId,
|
|
65
|
+
count: filtered.length,
|
|
66
|
+
avg: sum / filtered.length,
|
|
67
|
+
lastTs,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** All entries for a member, newest first. */
|
|
71
|
+
getEntries(memberId) {
|
|
72
|
+
return this.entries
|
|
73
|
+
.filter((e) => e.memberId === memberId)
|
|
74
|
+
.sort((a, b) => b.ts - a.ts);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Sort an array of `{id: string}` objects by their feedback score, descending.
|
|
78
|
+
* Members with no feedback are placed at the end, ordered by id ascending
|
|
79
|
+
* (deterministic). Ties broken by count desc, then id asc.
|
|
80
|
+
*
|
|
81
|
+
* Returns a NEW array — does not mutate the input.
|
|
82
|
+
*/
|
|
83
|
+
ranked(items) {
|
|
84
|
+
const stats = new Map();
|
|
85
|
+
for (const item of items) {
|
|
86
|
+
stats.set(item.id, this.getStats(item.id));
|
|
87
|
+
}
|
|
88
|
+
return [...items].sort((a, b) => {
|
|
89
|
+
const sa = stats.get(a.id);
|
|
90
|
+
const sb = stats.get(b.id);
|
|
91
|
+
if (sb.avg !== sa.avg)
|
|
92
|
+
return sb.avg - sa.avg;
|
|
93
|
+
if (sb.count !== sa.count)
|
|
94
|
+
return sb.count - sa.count;
|
|
95
|
+
return a.id.localeCompare(b.id);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Clear feedback entries. When `memberId` is given, only that member's
|
|
100
|
+
* entries are removed; otherwise the entire store is wiped. Returns the
|
|
101
|
+
* number of entries removed.
|
|
102
|
+
*/
|
|
103
|
+
clear(memberId) {
|
|
104
|
+
const before = this.entries.length;
|
|
105
|
+
if (memberId === undefined) {
|
|
106
|
+
this.entries = [];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
this.entries = this.entries.filter((e) => e.memberId !== memberId);
|
|
110
|
+
}
|
|
111
|
+
const removed = before - this.entries.length;
|
|
112
|
+
if (removed > 0)
|
|
113
|
+
this.save();
|
|
114
|
+
return removed;
|
|
115
|
+
}
|
|
116
|
+
/** All entries, newest first. Useful for `/council-feedback --list`. */
|
|
117
|
+
listAll() {
|
|
118
|
+
return [...this.entries].sort((a, b) => b.ts - a.ts);
|
|
119
|
+
}
|
|
120
|
+
// --- persistence ---------------------------------------------------------
|
|
121
|
+
load() {
|
|
122
|
+
if (!existsSync(this.file))
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
const raw = readFileSync(this.file, 'utf-8');
|
|
126
|
+
const parsed = JSON.parse(raw);
|
|
127
|
+
if (parsed && Array.isArray(parsed.entries)) {
|
|
128
|
+
// Defensive: only keep entries with valid shape.
|
|
129
|
+
this.entries = parsed.entries.filter((e) => e
|
|
130
|
+
&& typeof e.ts === 'number'
|
|
131
|
+
&& typeof e.memberId === 'string'
|
|
132
|
+
&& Number.isInteger(e.score)
|
|
133
|
+
&& e.score >= 1
|
|
134
|
+
&& e.score <= 5);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// Corrupt file — start fresh. Caller can recover via clear() + re-record.
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
save() {
|
|
142
|
+
mkdirSync(path.dirname(this.file), { recursive: true });
|
|
143
|
+
writeFileSync(this.file, JSON.stringify({ entries: this.entries }, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
144
|
+
}
|
|
145
|
+
/** Async variant of load for callers that prefer async IO. */
|
|
146
|
+
async loadAsync() {
|
|
147
|
+
try {
|
|
148
|
+
const raw = await fs.readFile(this.file, 'utf-8');
|
|
149
|
+
const parsed = JSON.parse(raw);
|
|
150
|
+
if (parsed && Array.isArray(parsed.entries)) {
|
|
151
|
+
this.entries = parsed.entries.filter((e) => e
|
|
152
|
+
&& typeof e.ts === 'number'
|
|
153
|
+
&& typeof e.memberId === 'string'
|
|
154
|
+
&& Number.isInteger(e.score)
|
|
155
|
+
&& e.score >= 1
|
|
156
|
+
&& e.score <= 5);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// ENOENT or JSON parse failure — fall back to in-memory state.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=councilFeedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"councilFeedback.js","sourceRoot":"","sources":["../../src/cli/councilFeedback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* crossProviderFailover — pure helpers for resolving the failover fallback
|
|
3
|
+
* stream based on the `ANATHEMA_FAILOVER_PROVIDER` env var (Task J.2, v3-J).
|
|
4
|
+
*
|
|
5
|
+
* Extracted from `app.tsx` `dispatchPrompt` so the resolution logic is
|
|
6
|
+
* testable without booting React/Ink. The wire site (`app.tsx`) calls
|
|
7
|
+
* `resolveFailoverStream(...)` once per prompt and passes the resulting
|
|
8
|
+
* stream + label to `providerFailover(...)`.
|
|
9
|
+
*
|
|
10
|
+
* Design:
|
|
11
|
+
* - All configuration flows in via a single `ResolveOptions` object.
|
|
12
|
+
* - The async `lookupFallbackConfig(id)` dependency is injected so tests
|
|
13
|
+
* can pass a stub without touching the real keyStore/providerConfig.
|
|
14
|
+
* - When `ANATHEMA_FAILOVER=0`, the helper returns `primary` untouched
|
|
15
|
+
* (master kill-switch from v3-G).
|
|
16
|
+
* - When the env var is unset / empty / unknown / missing key / same as
|
|
17
|
+
* primary, the helper returns `primary` as the fallback (v3-G
|
|
18
|
+
* behavior) and surfaces the reason via the returned `warning`.
|
|
19
|
+
* - When the env var resolves to a usable second provider, the helper
|
|
20
|
+
* returns the second stream with a label.
|
|
21
|
+
*
|
|
22
|
+
* @see docs/plans/2026-06-29-anathema-coder-v3-J.md
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Pure resolver for the failover fallback stream. Side-effect free: takes
|
|
26
|
+
* everything via `options`, returns a `ResolveResult`. Tests can stub the
|
|
27
|
+
* `lookupFallbackConfig` and `buildStream` functions to simulate any
|
|
28
|
+
* provider configuration without booting the full CLI.
|
|
29
|
+
*/
|
|
30
|
+
export async function resolveFailoverStream(options) {
|
|
31
|
+
if (!options.failoverEnabled) {
|
|
32
|
+
return {
|
|
33
|
+
fallback: options.primary,
|
|
34
|
+
fallbackLabel: undefined,
|
|
35
|
+
warning: '',
|
|
36
|
+
reason: 'disabled',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const requested = options.envValue?.trim();
|
|
40
|
+
if (!requested || requested.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
fallback: options.primary,
|
|
43
|
+
fallbackLabel: undefined,
|
|
44
|
+
warning: '',
|
|
45
|
+
reason: 'unset',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const valid = new Set(options.validProviderIds);
|
|
49
|
+
if (!valid.has(requested)) {
|
|
50
|
+
return {
|
|
51
|
+
fallback: options.primary,
|
|
52
|
+
fallbackLabel: undefined,
|
|
53
|
+
warning: `[failover] ANATHEMA_FAILOVER_PROVIDER="${requested}" is not a known provider. ` +
|
|
54
|
+
`Falling back to v3-G same-provider behavior. Available: ${options.validProviderIds.join(', ')}.`,
|
|
55
|
+
reason: 'unknown',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (requested === options.primaryProviderId) {
|
|
59
|
+
return {
|
|
60
|
+
fallback: options.primary,
|
|
61
|
+
fallbackLabel: undefined,
|
|
62
|
+
warning: '',
|
|
63
|
+
reason: 'same-as-primary',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const fallbackConfig = await options.lookupFallbackConfig(requested);
|
|
67
|
+
if (!fallbackConfig) {
|
|
68
|
+
return {
|
|
69
|
+
fallback: options.primary,
|
|
70
|
+
fallbackLabel: undefined,
|
|
71
|
+
warning: `[failover] No API key configured for provider "${requested}". ` +
|
|
72
|
+
`Falling back to v3-G same-provider behavior.`,
|
|
73
|
+
reason: 'missing-key',
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
fallback: options.buildStream(fallbackConfig),
|
|
78
|
+
fallbackLabel: requested,
|
|
79
|
+
warning: '',
|
|
80
|
+
reason: 'resolved',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=crossProviderFailover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossProviderFailover.js","sourceRoot":"","sources":["../../src/cli/crossProviderFailover.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAqDH;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,OAAuB;IAEvB,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QAC7B,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,OAAO;YACzB,aAAa,EAAE,SAAS;YACxB,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,UAAU;SACnB,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,OAAO;YACzB,aAAa,EAAE,SAAS;YACxB,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,OAAO;SAChB,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAS,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACxD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,OAAO;YACzB,aAAa,EAAE,SAAS;YACxB,OAAO,EACL,0CAA0C,SAAS,6BAA6B;gBAChF,2DAA2D,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACnG,MAAM,EAAE,SAAS;SAClB,CAAC;IACJ,CAAC;IACD,IAAI,SAAS,KAAK,OAAO,CAAC,iBAAiB,EAAE,CAAC;QAC5C,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,OAAO;YACzB,aAAa,EAAE,SAAS;YACxB,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,iBAAiB;SAC1B,CAAC;IACJ,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACrE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO;YACL,QAAQ,EAAE,OAAO,CAAC,OAAO;YACzB,aAAa,EAAE,SAAS;YACxB,OAAO,EACL,kDAAkD,SAAS,KAAK;gBAChE,8CAA8C;YAChD,MAAM,EAAE,aAAa;SACtB,CAAC;IACJ,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC;QAC7C,aAAa,EAAE,SAAS;QACxB,OAAO,EAAE,EAAE;QACX,MAAM,EAAE,UAAU;KACnB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gitOps — git-backed operations for slash commands.
|
|
3
|
+
*
|
|
4
|
+
* Exposes:
|
|
5
|
+
* - `getWorkingDiff(opts)` → returns the output of `git diff` (and optionally
|
|
6
|
+
* `git diff --cached`) as a string. Read-only, safe.
|
|
7
|
+
* - `undoWorkingChanges(opts)` → reverts working-tree modifications via
|
|
8
|
+
* `git checkout -- .` and `git reset HEAD` for staged changes.
|
|
9
|
+
* Destructive — callers should require an explicit `--yes` flag.
|
|
10
|
+
*
|
|
11
|
+
* Pure Node child_process — no Electron deps, browser-importable for tests.
|
|
12
|
+
*/
|
|
13
|
+
import { execFile } from 'node:child_process';
|
|
14
|
+
import { promisify } from 'node:util';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
/**
|
|
18
|
+
* Best-effort: run `git -C <cwd> <args>` and return stdout. Returns null on
|
|
19
|
+
* error (no git, not a repo, etc.). Never throws.
|
|
20
|
+
*/
|
|
21
|
+
async function git(cwd, args) {
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await execFileAsync('git', ['-C', cwd, ...args], {
|
|
24
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
25
|
+
});
|
|
26
|
+
return stdout;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** True if the working directory is inside a git working tree. */
|
|
33
|
+
export async function isGitRepo(cwd = process.cwd()) {
|
|
34
|
+
const out = await git(cwd, ['rev-parse', '--is-inside-work-tree']);
|
|
35
|
+
return out?.trim() === 'true';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Read the working-tree diff. Combines `git diff` (unstaged) and, when
|
|
39
|
+
* `staged: true`, `git diff --cached` (staged).
|
|
40
|
+
*/
|
|
41
|
+
export async function getWorkingDiff(opts = {}) {
|
|
42
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
43
|
+
const maxChars = opts.maxChars ?? 50_000;
|
|
44
|
+
const unstaged = (await git(cwd, ['diff'])) ?? '';
|
|
45
|
+
const staged = opts.staged
|
|
46
|
+
? ((await git(cwd, ['diff', '--cached'])) ?? '')
|
|
47
|
+
: '';
|
|
48
|
+
const combined = [staged, unstaged].filter((s) => s.length > 0).join('\n');
|
|
49
|
+
if (combined.length === 0) {
|
|
50
|
+
return { diff: '', truncated: false, empty: true };
|
|
51
|
+
}
|
|
52
|
+
if (combined.length > maxChars) {
|
|
53
|
+
return { diff: combined.slice(0, maxChars), truncated: true, empty: false };
|
|
54
|
+
}
|
|
55
|
+
return { diff: combined, truncated: false, empty: false };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Revert working-tree changes. Discards:
|
|
59
|
+
* - All unstaged modifications (`git checkout -- .`)
|
|
60
|
+
* - All untracked files are NOT touched (would be too dangerous — use a
|
|
61
|
+
* separate `git clean` command for that, intentionally not exposed here).
|
|
62
|
+
* - Optionally unstages everything (`git reset HEAD`).
|
|
63
|
+
*
|
|
64
|
+
* Returns the list of files that were touched, so callers can show feedback.
|
|
65
|
+
*/
|
|
66
|
+
export async function undoWorkingChanges(opts = {}) {
|
|
67
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
68
|
+
const unstage = opts.unstage !== false; // default true
|
|
69
|
+
// Snapshot the list of modified + staged files BEFORE the revert so we
|
|
70
|
+
// can report what changed. We exclude untracked ('??') and ignored ('!!')
|
|
71
|
+
// entries — undoing those would require `git clean`, which is intentionally
|
|
72
|
+
// NOT exposed here.
|
|
73
|
+
const statusBefore = (await git(cwd, ['status', '--porcelain'])) ?? '';
|
|
74
|
+
const modified = statusBefore
|
|
75
|
+
.split('\n')
|
|
76
|
+
.filter((line) => line.length > 0)
|
|
77
|
+
.filter((line) => !line.startsWith('??') && !line.startsWith('!!'))
|
|
78
|
+
.map((line) => line.slice(3).trim());
|
|
79
|
+
// 1. `git checkout -- .` — revert unstaged modifications.
|
|
80
|
+
await git(cwd, ['checkout', '--', '.']);
|
|
81
|
+
// 2. Optionally `git reset HEAD` — unstage everything.
|
|
82
|
+
if (unstage) {
|
|
83
|
+
await git(cwd, ['reset', 'HEAD', '--', '.']);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
reverted: modified,
|
|
87
|
+
unstaged: unstage ? modified : [],
|
|
88
|
+
summary: `Reverted ${modified.length} file(s)${unstage ? ' (also unstaged)' : ''}.`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Resolve the project root (the directory of this module's package.json) for convenience. */
|
|
92
|
+
export function defaultProjectRoot() {
|
|
93
|
+
// electron/cli/gitOps.ts → 3 levels up = project root.
|
|
94
|
+
return path.resolve(__dirname, '..', '..', '..');
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=gitOps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitOps.js","sourceRoot":"","sources":["../../src/cli/gitOps.ts"],"names":[],"mappings":"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"}
|