swarmdo 1.30.0 → 1.45.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/README.md +11 -6
- package/package.json +1 -1
- package/v3/@swarmdo/cli/dist/src/affected/affected.d.ts +6 -1
- package/v3/@swarmdo/cli/dist/src/affected/affected.js +7 -2
- package/v3/@swarmdo/cli/dist/src/apply/apply.js +24 -8
- package/v3/@swarmdo/cli/dist/src/changelog/changelog.d.ts +21 -0
- package/v3/@swarmdo/cli/dist/src/changelog/changelog.js +47 -1
- package/v3/@swarmdo/cli/dist/src/codegraph/codegraph.js +11 -1
- package/v3/@swarmdo/cli/dist/src/commands/changelog.js +15 -4
- package/v3/@swarmdo/cli/dist/src/commands/comms.d.ts +16 -0
- package/v3/@swarmdo/cli/dist/src/commands/comms.js +280 -0
- package/v3/@swarmdo/cli/dist/src/commands/compact.js +6 -2
- package/v3/@swarmdo/cli/dist/src/commands/config.js +7 -1
- package/v3/@swarmdo/cli/dist/src/commands/coupling.d.ts +17 -0
- package/v3/@swarmdo/cli/dist/src/commands/coupling.js +85 -0
- package/v3/@swarmdo/cli/dist/src/commands/hooks.js +176 -1
- package/v3/@swarmdo/cli/dist/src/commands/hotspots.js +10 -3
- package/v3/@swarmdo/cli/dist/src/commands/index.js +12 -3
- package/v3/@swarmdo/cli/dist/src/commands/permissions.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/commands/permissions.js +92 -0
- package/v3/@swarmdo/cli/dist/src/commands/redact.js +11 -0
- package/v3/@swarmdo/cli/dist/src/commands/release.js +27 -5
- package/v3/@swarmdo/cli/dist/src/commands/task.js +57 -2
- package/v3/@swarmdo/cli/dist/src/commands/usage.js +34 -1
- package/v3/@swarmdo/cli/dist/src/comms/mailbox.d.ts +88 -0
- package/v3/@swarmdo/cli/dist/src/comms/mailbox.js +138 -0
- package/v3/@swarmdo/cli/dist/src/comms/store.d.ts +21 -0
- package/v3/@swarmdo/cli/dist/src/comms/store.js +44 -0
- package/v3/@swarmdo/cli/dist/src/compact/compact.js +4 -1
- package/v3/@swarmdo/cli/dist/src/compact-snapshot/compact-snapshot.js +8 -3
- package/v3/@swarmdo/cli/dist/src/config-lint/agents-lint.d.ts +30 -0
- package/v3/@swarmdo/cli/dist/src/config-lint/agents-lint.js +87 -0
- package/v3/@swarmdo/cli/dist/src/config-lint/lint.d.ts +6 -1
- package/v3/@swarmdo/cli/dist/src/config-lint/lint.js +14 -4
- package/v3/@swarmdo/cli/dist/src/coupling/coupling.d.ts +56 -0
- package/v3/@swarmdo/cli/dist/src/coupling/coupling.js +86 -0
- package/v3/@swarmdo/cli/dist/src/env/env.js +40 -14
- package/v3/@swarmdo/cli/dist/src/hooks-recipe/command-guard.d.ts +46 -0
- package/v3/@swarmdo/cli/dist/src/hooks-recipe/command-guard.js +120 -0
- package/v3/@swarmdo/cli/dist/src/hooks-recipe/recipe.js +28 -1
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.d.ts +3 -0
- package/v3/@swarmdo/cli/dist/src/hotspots/hotspots.js +9 -0
- package/v3/@swarmdo/cli/dist/src/index.js +8 -0
- package/v3/@swarmdo/cli/dist/src/license/license.js +4 -1
- package/v3/@swarmdo/cli/dist/src/mcp-client.js +4 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/comms-tools.d.ts +11 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/comms-tools.js +93 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/coupling-tools.d.ts +14 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/coupling-tools.js +56 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/index.d.ts +2 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/index.js +2 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/profiles.js +1 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/task-deps.d.ts +35 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/task-deps.js +87 -0
- package/v3/@swarmdo/cli/dist/src/mcp-tools/task-tools.js +4 -0
- package/v3/@swarmdo/cli/dist/src/memory/memory-bridge.d.ts +2 -0
- package/v3/@swarmdo/cli/dist/src/memory/memory-bridge.js +4 -2
- package/v3/@swarmdo/cli/dist/src/memory-inject/select.d.ts +82 -0
- package/v3/@swarmdo/cli/dist/src/memory-inject/select.js +164 -0
- package/v3/@swarmdo/cli/dist/src/memory-vault/import.js +11 -1
- package/v3/@swarmdo/cli/dist/src/pack/pack.js +6 -2
- package/v3/@swarmdo/cli/dist/src/permissions/audit.d.ts +50 -0
- package/v3/@swarmdo/cli/dist/src/permissions/audit.js +127 -0
- package/v3/@swarmdo/cli/dist/src/plugins/store/search.js +1 -1
- package/v3/@swarmdo/cli/dist/src/redact/redact.js +22 -5
- package/v3/@swarmdo/cli/dist/src/redact/sarif.d.ts +28 -0
- package/v3/@swarmdo/cli/dist/src/redact/sarif.js +53 -0
- package/v3/@swarmdo/cli/dist/src/release/release.d.ts +19 -0
- package/v3/@swarmdo/cli/dist/src/release/release.js +29 -1
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.d.ts +2 -0
- package/v3/@swarmdo/cli/dist/src/sbom/sbom.js +5 -0
- package/v3/@swarmdo/cli/dist/src/swarmvector/semantic-router.js +8 -3
- package/v3/@swarmdo/cli/dist/src/testreport/testreport.js +9 -2
- package/v3/@swarmdo/cli/dist/src/transcript/export.js +8 -4
- package/v3/@swarmdo/cli/dist/src/update/checker.d.ts +1 -0
- package/v3/@swarmdo/cli/dist/src/update/checker.js +7 -2
- package/v3/@swarmdo/cli/dist/src/usage/model-efficiency.d.ts +29 -0
- package/v3/@swarmdo/cli/dist/src/usage/model-efficiency.js +28 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect-html.js +1 -0
- package/v3/@swarmdo/cli/dist/src/usage/reflect.d.ts +35 -1
- package/v3/@swarmdo/cli/dist/src/usage/reflect.js +58 -4
- package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.d.ts +8 -1
- package/v3/@swarmdo/cli/dist/src/usage/transcript-usage.js +21 -1
- package/v3/@swarmdo/cli/dist/src/util/since.d.ts +13 -0
- package/v3/@swarmdo/cli/dist/src/util/since.js +21 -0
- package/v3/@swarmdo/cli/dist/src/util/stdout.d.ts +23 -1
- package/v3/@swarmdo/cli/dist/src/util/stdout.js +34 -2
- package/v3/@swarmdo/cli/package.json +1 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* rows the parser already produces. See #47.
|
|
15
15
|
*/
|
|
16
16
|
import type { DayRow, ModelRow } from './diff.js';
|
|
17
|
+
import { type TranscriptModelPrice } from './claude-pricing.js';
|
|
17
18
|
export interface ModelShare {
|
|
18
19
|
model: string;
|
|
19
20
|
costUsd: number;
|
|
@@ -45,6 +46,8 @@ export interface Reflection {
|
|
|
45
46
|
costUsd: number;
|
|
46
47
|
totalTokens: number;
|
|
47
48
|
} | null;
|
|
49
|
+
/** active days whose cost is an outlier vs the median (possible runaways) */
|
|
50
|
+
spikeDays: SpikeDay[];
|
|
48
51
|
/** models ranked by cost desc (capped to opts.topModels) */
|
|
49
52
|
topModels: ModelShare[];
|
|
50
53
|
/** projects ranked by cost desc (ModelShare.model holds the project path) */
|
|
@@ -68,12 +71,16 @@ export interface Reflection {
|
|
|
68
71
|
secondHalfCost: number;
|
|
69
72
|
direction: 'up' | 'down' | 'flat';
|
|
70
73
|
};
|
|
74
|
+
/** $ prompt-caching saved this period (vs paying full input rate); 0 if no priced models */
|
|
75
|
+
cacheSavingsUsd: number;
|
|
71
76
|
}
|
|
72
77
|
export interface ReflectOptions {
|
|
73
78
|
/** how many models to keep in topModels (default 5) */
|
|
74
79
|
topModels?: number;
|
|
75
80
|
/** relative change below which the trend reads 'flat' (default 0.05 = 5%) */
|
|
76
81
|
flatThreshold?: number;
|
|
82
|
+
/** price resolver for cache-savings (injected for tests; default = the real table) */
|
|
83
|
+
resolvePrice?: (model: string) => TranscriptModelPrice | undefined;
|
|
77
84
|
}
|
|
78
85
|
/** The calendar day after an ISO date (handles month/year rollover). Pure. */
|
|
79
86
|
export declare function nextDay(iso: string): string;
|
|
@@ -83,10 +90,37 @@ export declare function nextDay(iso: string): string;
|
|
|
83
90
|
* Pure. Used to derive a `--period 1m|3m|…` window start from today.
|
|
84
91
|
*/
|
|
85
92
|
export declare function monthsBefore(iso: string, n: number): string;
|
|
86
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Inclusive whole-day span between two ISO dates (from <= to). Pure.
|
|
95
|
+
*
|
|
96
|
+
* Counts on the UTC timeline (`Date.UTC`), NOT local midnights: a local
|
|
97
|
+
* `new Date('…T00:00:00')` is DST-sensitive, so a range crossing a spring-forward
|
|
98
|
+
* day (only 23h long) would floor-divide to one day short — e.g. under
|
|
99
|
+
* TZ=America/New_York a full March would read 30. UTC calendar midnights are
|
|
100
|
+
* always an exact 86.4M ms apart, so this is correct in every process timezone.
|
|
101
|
+
*/
|
|
87
102
|
export declare function spanDays(from: string, to: string): number;
|
|
88
103
|
/** Longest run of consecutive calendar days present in the set. Pure. */
|
|
89
104
|
export declare function longestStreakOf(days: string[]): number;
|
|
105
|
+
export interface SpikeDay {
|
|
106
|
+
day: string;
|
|
107
|
+
costUsd: number;
|
|
108
|
+
/** costUsd / median active-day cost */
|
|
109
|
+
ratioToMedian: number;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Days whose cost is an outlier vs the median active-day cost — a heads-up for
|
|
113
|
+
* runaway sessions. Median (not mean) so the spikes don't inflate the baseline.
|
|
114
|
+
* Needs ≥3 active days for a meaningful median; a floor keeps trivially-cheap
|
|
115
|
+
* days from tripping the ratio. Sorted by cost desc. Pure.
|
|
116
|
+
*/
|
|
117
|
+
export declare function detectSpikeDays(days: Array<{
|
|
118
|
+
day: string;
|
|
119
|
+
costUsd: number;
|
|
120
|
+
}>, opts?: {
|
|
121
|
+
minRatio?: number;
|
|
122
|
+
minCostUsd?: number;
|
|
123
|
+
}): SpikeDay[];
|
|
90
124
|
/** Render a numeric series as a Unicode block sparkline (one char per bucket).
|
|
91
125
|
* Zero buckets are blank; the rest scale ▁..█ against the max. Pure — used for
|
|
92
126
|
* the terminal cost-by-hour view (the HTML dashboard draws the same data). */
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* without touching disk. The command layer in ../commands/usage.ts feeds it the
|
|
14
14
|
* rows the parser already produces. See #47.
|
|
15
15
|
*/
|
|
16
|
+
import { modelCacheSavings } from './cache-stats.js';
|
|
17
|
+
import { resolveTranscriptPrice } from './claude-pricing.js';
|
|
16
18
|
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
17
19
|
/** The calendar day after an ISO date (handles month/year rollover). Pure. */
|
|
18
20
|
export function nextDay(iso) {
|
|
@@ -38,11 +40,21 @@ export function monthsBefore(iso, n) {
|
|
|
38
40
|
const dd = String(dt.getDate()).padStart(2, '0');
|
|
39
41
|
return `${yy}-${mm}-${dd}`;
|
|
40
42
|
}
|
|
41
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Inclusive whole-day span between two ISO dates (from <= to). Pure.
|
|
45
|
+
*
|
|
46
|
+
* Counts on the UTC timeline (`Date.UTC`), NOT local midnights: a local
|
|
47
|
+
* `new Date('…T00:00:00')` is DST-sensitive, so a range crossing a spring-forward
|
|
48
|
+
* day (only 23h long) would floor-divide to one day short — e.g. under
|
|
49
|
+
* TZ=America/New_York a full March would read 30. UTC calendar midnights are
|
|
50
|
+
* always an exact 86.4M ms apart, so this is correct in every process timezone.
|
|
51
|
+
*/
|
|
42
52
|
export function spanDays(from, to) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
53
|
+
const [ay, am, ad] = from.split('-').map(Number);
|
|
54
|
+
const [by, bm, bd] = to.split('-').map(Number);
|
|
55
|
+
const a = Date.UTC(ay, am - 1, ad);
|
|
56
|
+
const b = Date.UTC(by, bm - 1, bd);
|
|
57
|
+
return Math.round((b - a) / 86_400_000) + 1;
|
|
46
58
|
}
|
|
47
59
|
/** Longest run of consecutive calendar days present in the set. Pure. */
|
|
48
60
|
export function longestStreakOf(days) {
|
|
@@ -58,6 +70,27 @@ export function longestStreakOf(days) {
|
|
|
58
70
|
}
|
|
59
71
|
return best;
|
|
60
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Days whose cost is an outlier vs the median active-day cost — a heads-up for
|
|
75
|
+
* runaway sessions. Median (not mean) so the spikes don't inflate the baseline.
|
|
76
|
+
* Needs ≥3 active days for a meaningful median; a floor keeps trivially-cheap
|
|
77
|
+
* days from tripping the ratio. Sorted by cost desc. Pure.
|
|
78
|
+
*/
|
|
79
|
+
export function detectSpikeDays(days, opts = {}) {
|
|
80
|
+
const minRatio = opts.minRatio ?? 2.5;
|
|
81
|
+
const minCostUsd = opts.minCostUsd ?? 0.5;
|
|
82
|
+
const costs = days.map((d) => d.costUsd).filter((c) => c > 0).sort((a, b) => a - b);
|
|
83
|
+
if (costs.length < 3)
|
|
84
|
+
return [];
|
|
85
|
+
const mid = costs.length >> 1;
|
|
86
|
+
const median = costs.length % 2 ? costs[mid] : (costs[mid - 1] + costs[mid]) / 2;
|
|
87
|
+
if (median <= 0)
|
|
88
|
+
return [];
|
|
89
|
+
return days
|
|
90
|
+
.filter((d) => d.costUsd >= minCostUsd && d.costUsd >= minRatio * median)
|
|
91
|
+
.map((d) => ({ day: d.day, costUsd: d.costUsd, ratioToMedian: d.costUsd / median }))
|
|
92
|
+
.sort((a, b) => b.costUsd - a.costUsd);
|
|
93
|
+
}
|
|
61
94
|
const within = (key, from, to) => key >= from && key <= to;
|
|
62
95
|
const SPARK_BLOCKS = ' ▁▂▃▄▅▆▇█'; // index 0 = empty (zero), 1..8 = ▁..█ scaled to max
|
|
63
96
|
/** Render a numeric series as a Unicode block sparkline (one char per bucket).
|
|
@@ -145,6 +178,25 @@ export function computeReflection(dayRows, modelRows, from, to, opts = {}, proje
|
|
|
145
178
|
// Cost-ranked shares over the same window, for both models and projects.
|
|
146
179
|
const topModels = rankShares(modelRows, from, to, totals.costUsd, topN);
|
|
147
180
|
const topProjects = rankShares(projectRows, from, to, totals.costUsd, topN);
|
|
181
|
+
// $ saved by prompt caching this period: fold windowed per-model token counts,
|
|
182
|
+
// then price each via the (injected) resolver. Unpriced models contribute 0.
|
|
183
|
+
const resolvePrice = opts.resolvePrice ?? resolveTranscriptPrice;
|
|
184
|
+
const cacheTokens = new Map();
|
|
185
|
+
for (const r of modelRows) {
|
|
186
|
+
if (!within(r.day, from, to))
|
|
187
|
+
continue;
|
|
188
|
+
const s = cacheTokens.get(r.key) ?? { inputTokens: 0, cacheWriteTokens: 0, cacheReadTokens: 0 };
|
|
189
|
+
s.inputTokens += r.totals.inputTokens;
|
|
190
|
+
s.cacheWriteTokens += r.totals.cacheWriteTokens;
|
|
191
|
+
s.cacheReadTokens += r.totals.cacheReadTokens;
|
|
192
|
+
cacheTokens.set(r.key, s);
|
|
193
|
+
}
|
|
194
|
+
let cacheSavingsUsd = 0;
|
|
195
|
+
for (const [model, tok] of cacheTokens) {
|
|
196
|
+
const sv = modelCacheSavings(tok, resolvePrice(model));
|
|
197
|
+
if (sv !== null)
|
|
198
|
+
cacheSavingsUsd += sv;
|
|
199
|
+
}
|
|
148
200
|
const inputSide = totals.inputTokens + totals.cacheWriteTokens + totals.cacheReadTokens;
|
|
149
201
|
const relChange = firstHalfCost > 0 ? (secondHalfCost - firstHalfCost) / firstHalfCost : (secondHalfCost > 0 ? 1 : 0);
|
|
150
202
|
const direction = Math.abs(relChange) < flat ? 'flat' : relChange > 0 ? 'up' : 'down';
|
|
@@ -152,6 +204,7 @@ export function computeReflection(dayRows, modelRows, from, to, opts = {}, proje
|
|
|
152
204
|
period: { from, to, spanDays: spanDays(from, to) },
|
|
153
205
|
totals,
|
|
154
206
|
busiestDay,
|
|
207
|
+
spikeDays: detectSpikeDays(days.map((r) => ({ day: r.key, costUsd: r.totals.costUsd }))),
|
|
155
208
|
topModels,
|
|
156
209
|
topProjects,
|
|
157
210
|
peakHour: peakHourOf(hourHistogram),
|
|
@@ -160,6 +213,7 @@ export function computeReflection(dayRows, modelRows, from, to, opts = {}, proje
|
|
|
160
213
|
cacheReadPct: inputSide > 0 ? totals.cacheReadTokens / inputSide : 0,
|
|
161
214
|
avgCostPerActiveDay: totals.activeDays > 0 ? totals.costUsd / totals.activeDays : 0,
|
|
162
215
|
trend: { firstHalfCost, secondHalfCost, direction },
|
|
216
|
+
cacheSavingsUsd,
|
|
163
217
|
};
|
|
164
218
|
}
|
|
165
219
|
//# sourceMappingURL=reflect.js.map
|
|
@@ -77,7 +77,14 @@ export declare function normalizeDateBound(raw: string | undefined): string | un
|
|
|
77
77
|
* report must not fail because one session file is mid-write.
|
|
78
78
|
*/
|
|
79
79
|
export declare function collectUsage(opts?: CollectOptions): UsageCollection;
|
|
80
|
-
export type UsageDimension = 'day' | 'month' | 'model' | 'project' | 'session';
|
|
80
|
+
export type UsageDimension = 'day' | 'week' | 'month' | 'model' | 'project' | 'session';
|
|
81
|
+
/**
|
|
82
|
+
* ISO-8601 week key for a `YYYY-MM-DD` date, e.g. '2026-W28'. The week belongs to
|
|
83
|
+
* the year of its Thursday, so a late-December / early-January week can carry the
|
|
84
|
+
* neighbouring ISO year (e.g. 2025-12-31 → 2026-W01, 2027-01-01 → 2026-W53).
|
|
85
|
+
* Keys sort chronologically as strings. Pure.
|
|
86
|
+
*/
|
|
87
|
+
export declare function isoWeekKey(iso: string): string;
|
|
81
88
|
/**
|
|
82
89
|
* Group events along one dimension. Time dimensions sort chronologically;
|
|
83
90
|
* the rest sort by descending cost (what a "top spenders" view wants).
|
|
@@ -209,8 +209,28 @@ function toUsageEvent(line, project, seen, unpriced) {
|
|
|
209
209
|
costSource,
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* ISO-8601 week key for a `YYYY-MM-DD` date, e.g. '2026-W28'. The week belongs to
|
|
214
|
+
* the year of its Thursday, so a late-December / early-January week can carry the
|
|
215
|
+
* neighbouring ISO year (e.g. 2025-12-31 → 2026-W01, 2027-01-01 → 2026-W53).
|
|
216
|
+
* Keys sort chronologically as strings. Pure.
|
|
217
|
+
*/
|
|
218
|
+
export function isoWeekKey(iso) {
|
|
219
|
+
const [y, m, d] = iso.split('-').map(Number);
|
|
220
|
+
const date = new Date(Date.UTC(y, m - 1, d));
|
|
221
|
+
const dayNum = (date.getUTCDay() + 6) % 7; // Mon=0 … Sun=6
|
|
222
|
+
date.setUTCDate(date.getUTCDate() - dayNum + 3); // Thursday of this week
|
|
223
|
+
const isoYear = date.getUTCFullYear();
|
|
224
|
+
const jan4 = new Date(Date.UTC(isoYear, 0, 4)); // Jan 4 is always in week 1
|
|
225
|
+
const jan4Day = (jan4.getUTCDay() + 6) % 7;
|
|
226
|
+
const week1Monday = new Date(jan4);
|
|
227
|
+
week1Monday.setUTCDate(jan4.getUTCDate() - jan4Day);
|
|
228
|
+
const week = 1 + Math.round((date.getTime() - week1Monday.getTime()) / (7 * 86_400_000));
|
|
229
|
+
return `${isoYear}-W${String(week).padStart(2, '0')}`;
|
|
230
|
+
}
|
|
212
231
|
const DIMENSION_KEY = {
|
|
213
232
|
day: (e) => e.dateKey,
|
|
233
|
+
week: (e) => isoWeekKey(e.dateKey),
|
|
214
234
|
month: (e) => e.monthKey,
|
|
215
235
|
model: (e) => e.model,
|
|
216
236
|
project: (e) => e.project,
|
|
@@ -253,7 +273,7 @@ export function aggregateUsage(events, dimension) {
|
|
|
253
273
|
addEvent(t, e);
|
|
254
274
|
}
|
|
255
275
|
const rows = Array.from(groups.entries()).map(([key, totals]) => ({ key, totals }));
|
|
256
|
-
if (dimension === 'day' || dimension === 'month') {
|
|
276
|
+
if (dimension === 'day' || dimension === 'week' || dimension === 'month') {
|
|
257
277
|
rows.sort((a, b) => a.key.localeCompare(b.key));
|
|
258
278
|
}
|
|
259
279
|
else {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* since.ts — normalize compact age windows for `git log --since`.
|
|
3
|
+
*
|
|
4
|
+
* git's approxidate parser does NOT understand the bare compact form `90d`
|
|
5
|
+
* (it silently matches nothing — `git log --since=90d` returns zero commits),
|
|
6
|
+
* yet `hotspots`/`coupling` document `--since 90d`. This maps the compact forms
|
|
7
|
+
* (`90d`, `6mo`, `2w`, `1y`, `3h`) to the spelled-out `"90 days ago"` git DOES
|
|
8
|
+
* understand. Anything already spelled out (`"3 months ago"`), an ISO date, or
|
|
9
|
+
* an unrecognized token passes through unchanged. Pure + unit-tested.
|
|
10
|
+
*/
|
|
11
|
+
/** Convert `90d`→`90 days ago`, `6mo`→`6 months ago`, etc. Passthrough otherwise. */
|
|
12
|
+
export declare function normalizeSince(since: string): string;
|
|
13
|
+
//# sourceMappingURL=since.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* since.ts — normalize compact age windows for `git log --since`.
|
|
3
|
+
*
|
|
4
|
+
* git's approxidate parser does NOT understand the bare compact form `90d`
|
|
5
|
+
* (it silently matches nothing — `git log --since=90d` returns zero commits),
|
|
6
|
+
* yet `hotspots`/`coupling` document `--since 90d`. This maps the compact forms
|
|
7
|
+
* (`90d`, `6mo`, `2w`, `1y`, `3h`) to the spelled-out `"90 days ago"` git DOES
|
|
8
|
+
* understand. Anything already spelled out (`"3 months ago"`), an ISO date, or
|
|
9
|
+
* an unrecognized token passes through unchanged. Pure + unit-tested.
|
|
10
|
+
*/
|
|
11
|
+
const COMPACT = /^\s*(\d+)\s*(d|days?|w|wks?|weeks?|mo|mons?|months?|y|yrs?|years?|h|hrs?|hours?)\s*$/i;
|
|
12
|
+
/** Convert `90d`→`90 days ago`, `6mo`→`6 months ago`, etc. Passthrough otherwise. */
|
|
13
|
+
export function normalizeSince(since) {
|
|
14
|
+
const m = COMPACT.exec(since);
|
|
15
|
+
if (!m)
|
|
16
|
+
return since;
|
|
17
|
+
const c0 = m[2][0].toLowerCase(); // the allowed units are uniquely keyed by first letter
|
|
18
|
+
const unit = c0 === 'd' ? 'days' : c0 === 'w' ? 'weeks' : c0 === 'y' ? 'years' : c0 === 'h' ? 'hours' : 'months';
|
|
19
|
+
return `${m[1]} ${unit} ago`;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=since.js.map
|
|
@@ -7,6 +7,28 @@
|
|
|
7
7
|
* (`swarmdo sbom | …`, `pack`, `compact`, `redact`) truncates at the ~64 KiB
|
|
8
8
|
* pipe buffer. Awaiting the write callback guarantees the data reached the OS
|
|
9
9
|
* before we hand control back. For a TTY (synchronous) this is a no-op await.
|
|
10
|
+
*
|
|
11
|
+
* Broken-pipe (EPIPE) handling: when the reader closes the pipe early (`… |
|
|
12
|
+
* head`), Node emits an `'error'` event on the stdout Socket — often
|
|
13
|
+
* ASYNCHRONOUSLY, after our write callback has already fired — and an unhandled
|
|
14
|
+
* `'error'` event crashes the process with a raw stack trace. A per-write
|
|
15
|
+
* listener therefore doesn't cover it (it's gone by the time the async error
|
|
16
|
+
* lands). So we install ONE persistent EPIPE guard per stream that swallows the
|
|
17
|
+
* broken pipe (normal producer termination) while still rethrowing any other
|
|
18
|
+
* stream error, and additionally resolve the write promise on an EPIPE callback.
|
|
19
|
+
* The `stream` param is injectable so this is unit-testable without a subprocess.
|
|
20
|
+
*/
|
|
21
|
+
/** The subset of a writable stream writeStdout needs (process.stdout satisfies it). */
|
|
22
|
+
export interface WritableLike {
|
|
23
|
+
write(data: string, cb: (err?: Error | null) => void): boolean;
|
|
24
|
+
on(event: 'error', listener: (err: Error) => void): unknown;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Install (once per stream) a persistent `'error'` handler that swallows EPIPE —
|
|
28
|
+
* a closed reader is normal termination for a Unix producer, not a crash — and
|
|
29
|
+
* rethrows everything else so genuine stream errors still surface. Exported for
|
|
30
|
+
* the CLI entry point to arm stdout/stderr before any write happens.
|
|
10
31
|
*/
|
|
11
|
-
export declare function
|
|
32
|
+
export declare function guardStreamEpipe(stream: WritableLike): void;
|
|
33
|
+
export declare function writeStdout(data: string, stream?: WritableLike): Promise<void>;
|
|
12
34
|
//# sourceMappingURL=stdout.d.ts.map
|
|
@@ -7,10 +7,42 @@
|
|
|
7
7
|
* (`swarmdo sbom | …`, `pack`, `compact`, `redact`) truncates at the ~64 KiB
|
|
8
8
|
* pipe buffer. Awaiting the write callback guarantees the data reached the OS
|
|
9
9
|
* before we hand control back. For a TTY (synchronous) this is a no-op await.
|
|
10
|
+
*
|
|
11
|
+
* Broken-pipe (EPIPE) handling: when the reader closes the pipe early (`… |
|
|
12
|
+
* head`), Node emits an `'error'` event on the stdout Socket — often
|
|
13
|
+
* ASYNCHRONOUSLY, after our write callback has already fired — and an unhandled
|
|
14
|
+
* `'error'` event crashes the process with a raw stack trace. A per-write
|
|
15
|
+
* listener therefore doesn't cover it (it's gone by the time the async error
|
|
16
|
+
* lands). So we install ONE persistent EPIPE guard per stream that swallows the
|
|
17
|
+
* broken pipe (normal producer termination) while still rethrowing any other
|
|
18
|
+
* stream error, and additionally resolve the write promise on an EPIPE callback.
|
|
19
|
+
* The `stream` param is injectable so this is unit-testable without a subprocess.
|
|
20
|
+
*/
|
|
21
|
+
const guarded = new WeakSet();
|
|
22
|
+
/**
|
|
23
|
+
* Install (once per stream) a persistent `'error'` handler that swallows EPIPE —
|
|
24
|
+
* a closed reader is normal termination for a Unix producer, not a crash — and
|
|
25
|
+
* rethrows everything else so genuine stream errors still surface. Exported for
|
|
26
|
+
* the CLI entry point to arm stdout/stderr before any write happens.
|
|
10
27
|
*/
|
|
11
|
-
export function
|
|
28
|
+
export function guardStreamEpipe(stream) {
|
|
29
|
+
if (guarded.has(stream))
|
|
30
|
+
return;
|
|
31
|
+
guarded.add(stream);
|
|
32
|
+
stream.on('error', (err) => {
|
|
33
|
+
if (err.code !== 'EPIPE')
|
|
34
|
+
throw err;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export function writeStdout(data, stream = process.stdout) {
|
|
38
|
+
guardStreamEpipe(stream);
|
|
12
39
|
return new Promise((resolve, reject) => {
|
|
13
|
-
|
|
40
|
+
stream.write(data, (err) => {
|
|
41
|
+
if (err && err.code !== 'EPIPE')
|
|
42
|
+
reject(err);
|
|
43
|
+
else
|
|
44
|
+
resolve();
|
|
45
|
+
});
|
|
14
46
|
});
|
|
15
47
|
}
|
|
16
48
|
//# sourceMappingURL=stdout.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmdo/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Swarmdo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|