@contentful/experience-design-system-cli 2.26.1-dev-build-a852362.0 → 2.26.1-dev-build-f03f014.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/package.json +14 -10
- package/dist/src/index.js +34937 -9
- package/dist/src/lib/cli-path.d.ts +4 -0
- package/dist/src/runs/export-helpers.d.ts +1 -12
- package/dist/src/runs/push-helpers.d.ts +1 -6
- package/package.json +12 -7
- package/skills/generate-components.md +427 -0
- package/skills/generate-tokens.md +194 -0
- package/skills/select-components.md +224 -0
- package/dist/src/analytics/apply.js +0 -30
- package/dist/src/analytics/client.js +0 -81
- package/dist/src/analytics/constants.js +0 -4
- package/dist/src/analytics/env.js +0 -14
- package/dist/src/analytics/exit.js +0 -24
- package/dist/src/analytics/index.js +0 -9
- package/dist/src/analytics/normalize.js +0 -18
- package/dist/src/analytics/os.js +0 -13
- package/dist/src/analytics/session.js +0 -15
- package/dist/src/analytics/tracker.js +0 -126
- package/dist/src/analytics/types.js +0 -1
- package/dist/src/analyze/build-analyze-view-rows.js +0 -63
- package/dist/src/analyze/command.js +0 -599
- package/dist/src/analyze/composite-closure.js +0 -0
- package/dist/src/analyze/composition/agent-parser/author-prompt.js +0 -22
- package/dist/src/analyze/composition/agent-parser/extract-parser.js +0 -44
- package/dist/src/analyze/composition/agent-parser/load-prompt.js +0 -31
- package/dist/src/analyze/composition/agent-parser/resolve-via-parser.js +0 -88
- package/dist/src/analyze/composition/agent-parser/sandbox.js +0 -187
- package/dist/src/analyze/composition/apply-mapping.js +0 -67
- package/dist/src/analyze/composition/candidate-critic-agent.js +0 -39
- package/dist/src/analyze/composition/candidate-critic.js +0 -63
- package/dist/src/analyze/composition/candidate-files.js +0 -131
- package/dist/src/analyze/composition/composition-cache-key.js +0 -19
- package/dist/src/analyze/composition/interchange-schema.js +0 -61
- package/dist/src/analyze/composition/manifest-doc-evidence.js +0 -136
- package/dist/src/analyze/composition/merge-edges.js +0 -70
- package/dist/src/analyze/composition/parse-map-edges.js +0 -55
- package/dist/src/analyze/composition/resolve-mapping-cli.js +0 -29
- package/dist/src/analyze/composition/resolve-mapping.js +0 -111
- package/dist/src/analyze/cycle-detection.js +0 -234
- package/dist/src/analyze/cycle-view.js +0 -28
- package/dist/src/analyze/fuzzy-search.js +0 -55
- package/dist/src/analyze/issue-inheritance.js +0 -108
- package/dist/src/analyze/lineage.js +0 -0
- package/dist/src/analyze/scope-gate-cascade.js +0 -170
- package/dist/src/analyze/search-neighborhood.js +0 -56
- package/dist/src/analyze/select/command.js +0 -484
- package/dist/src/analyze/select/index.js +0 -5
- package/dist/src/analyze/select/parser.js +0 -53
- package/dist/src/analyze/select/persistence.js +0 -80
- package/dist/src/analyze/select/preview-annotations.js +0 -62
- package/dist/src/analyze/select/stdout.js +0 -3
- package/dist/src/analyze/select/tui/App.js +0 -483
- package/dist/src/analyze/select/tui/components/ComponentDetail.js +0 -48
- package/dist/src/analyze/select/tui/components/ComponentRationalePanel.js +0 -125
- package/dist/src/analyze/select/tui/components/FieldEditor.js +0 -1131
- package/dist/src/analyze/select/tui/components/FinalizeDialog.js +0 -36
- package/dist/src/analyze/select/tui/components/GotoBanner.js +0 -30
- package/dist/src/analyze/select/tui/components/GroupedSidebar.js +0 -517
- package/dist/src/analyze/select/tui/components/HelpOverlay.js +0 -17
- package/dist/src/analyze/select/tui/components/JsonEditor.js +0 -131
- package/dist/src/analyze/select/tui/components/JsonPanel.js +0 -66
- package/dist/src/analyze/select/tui/components/LineagePanel.js +0 -22
- package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +0 -29
- package/dist/src/analyze/select/tui/components/QuitDialog.js +0 -14
- package/dist/src/analyze/select/tui/components/RationalePanel.js +0 -80
- package/dist/src/analyze/select/tui/components/Sidebar.js +0 -92
- package/dist/src/analyze/select/tui/components/SourcePanel.js +0 -52
- package/dist/src/analyze/select/tui/components/StatusBar.js +0 -7
- package/dist/src/analyze/select/tui/components/TopBar.js +0 -10
- package/dist/src/analyze/select/tui/components/removed-components-text.js +0 -11
- package/dist/src/analyze/select/tui/hooks/scroll-offset.js +0 -26
- package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +0 -66
- package/dist/src/analyze/select/tui/hooks/useKeymap.js +0 -67
- package/dist/src/analyze/select/tui/hooks/useSession.js +0 -52
- package/dist/src/analyze/select/tui/hooks/useUndo.js +0 -26
- package/dist/src/analyze/select/tui/theme.js +0 -28
- package/dist/src/analyze/select/types.js +0 -39
- package/dist/src/analyze/select-agent/command.js +0 -558
- package/dist/src/analyze/select-agent/context-builder.js +0 -197
- package/dist/src/analyze/select-agent/show-rationale.js +0 -92
- package/dist/src/analyze/selection-cascade.js +0 -19
- package/dist/src/analyze/slot-graph.js +0 -17
- package/dist/src/analyze/tui/AnalyzeView.js +0 -64
- package/dist/src/apply/api-client.js +0 -440
- package/dist/src/apply/command.js +0 -845
- package/dist/src/apply/manifest.js +0 -1
- package/dist/src/apply/preview-utils.js +0 -26
- package/dist/src/apply/tui/SelectView.js +0 -34
- package/dist/src/apply/tui/ServerApplyView.js +0 -49
- package/dist/src/apply/tui/ServerPreviewView.js +0 -23
- package/dist/src/credentials-store.js +0 -74
- package/dist/src/generate/command.js +0 -597
- package/dist/src/generate/edit/command.js +0 -127
- package/dist/src/generate/tui/GenerateView.js +0 -10
- package/dist/src/host-utils.js +0 -26
- package/dist/src/import/agent-model-resolve.js +0 -35
- package/dist/src/import/auto-filter-resolve.js +0 -16
- package/dist/src/import/command.js +0 -380
- package/dist/src/import/cycle-auto-reject.js +0 -14
- package/dist/src/import/orchestrator.js +0 -603
- package/dist/src/import/path-utils.js +0 -30
- package/dist/src/import/picker-dispatch.js +0 -44
- package/dist/src/import/print-prompt.js +0 -31
- package/dist/src/import/strip-allowed-components.js +0 -22
- package/dist/src/import/tui/CustomPromptBanner.js +0 -8
- package/dist/src/import/tui/WizardApp.js +0 -1670
- package/dist/src/import/tui/ai-flag.js +0 -6
- package/dist/src/import/tui/auto-filter-error.js +0 -29
- package/dist/src/import/tui/autocomplete.js +0 -32
- package/dist/src/import/tui/components/AutoFilterBanner.js +0 -19
- package/dist/src/import/tui/components/CounterStrip.js +0 -12
- package/dist/src/import/tui/components/LegendEntry.js +0 -6
- package/dist/src/import/tui/cycle-panel-scroll.js +0 -33
- package/dist/src/import/tui/final-review-host.js +0 -25
- package/dist/src/import/tui/group-collapse.js +0 -11
- package/dist/src/import/tui/history.js +0 -53
- package/dist/src/import/tui/hooks/useLineage.js +0 -50
- package/dist/src/import/tui/hooks/useOverlayPanel.js +0 -27
- package/dist/src/import/tui/lineage-layout.js +0 -82
- package/dist/src/import/tui/merge-ai-decisions.js +0 -22
- package/dist/src/import/tui/push-decision-gate-helpers.js +0 -8
- package/dist/src/import/tui/push-progress.js +0 -1
- package/dist/src/import/tui/run-print-files-helpers.js +0 -8
- package/dist/src/import/tui/run-teaser.js +0 -9
- package/dist/src/import/tui/runLivePreview.js +0 -82
- package/dist/src/import/tui/runScopeGate.js +0 -28
- package/dist/src/import/tui/scope-gate-columns.js +0 -94
- package/dist/src/import/tui/scope-gate-host.js +0 -28
- package/dist/src/import/tui/sidebar-width.js +0 -3
- package/dist/src/import/tui/spawn-generate.js +0 -53
- package/dist/src/import/tui/step-filters.js +0 -42
- package/dist/src/import/tui/steps/AtomicGenerateReviewStep.js +0 -726
- package/dist/src/import/tui/steps/AtomicScopeGateStep.js +0 -210
- package/dist/src/import/tui/steps/CredentialsStep.js +0 -108
- package/dist/src/import/tui/steps/DoneStep.js +0 -24
- package/dist/src/import/tui/steps/ErrorStep.js +0 -16
- package/dist/src/import/tui/steps/GateStep.js +0 -22
- package/dist/src/import/tui/steps/GenerateReviewStep.js +0 -1618
- package/dist/src/import/tui/steps/PathValidationStep.js +0 -152
- package/dist/src/import/tui/steps/PreviewStep.js +0 -37
- package/dist/src/import/tui/steps/PreviewValidationErrorStep.js +0 -23
- package/dist/src/import/tui/steps/PushDecisionGateStep.js +0 -73
- package/dist/src/import/tui/steps/PushingStep.js +0 -24
- package/dist/src/import/tui/steps/RunningStep.js +0 -21
- package/dist/src/import/tui/steps/ScopeGateStep.js +0 -907
- package/dist/src/import/tui/steps/TokenInputStep.js +0 -88
- package/dist/src/import/tui/steps/WelcomeStep.js +0 -34
- package/dist/src/import/tui/steps/WizardPreviewStep.js +0 -169
- package/dist/src/import/tui/steps/auto-reject-decision.js +0 -9
- package/dist/src/import/tui/steps/breaking-change-format.js +0 -24
- package/dist/src/import/tui/steps/enumerate-cycle-breaks.js +0 -0
- package/dist/src/import/tui/steps/preview-diff.js +0 -175
- package/dist/src/import/tui/useFinalizePreview.js +0 -0
- package/dist/src/import/tui/useLivePreview.js +0 -122
- package/dist/src/import/tui/wizard-422-helpers.js +0 -60
- package/dist/src/import/tui/wizard-generate-progress.js +0 -25
- package/dist/src/import/tui/wizard-save-flow.js +0 -20
- package/dist/src/import/tui/wizard-state-transitions.js +0 -32
- package/dist/src/lib/agent-model-options.js +0 -15
- package/dist/src/lib/command-options.js +0 -30
- package/dist/src/lib/composition-mode.js +0 -39
- package/dist/src/lib/contentful-urls.js +0 -27
- package/dist/src/lib/debug-logger.js +0 -229
- package/dist/src/lib/debug-preamble.js +0 -33
- package/dist/src/lib/error-parser.js +0 -269
- package/dist/src/lib/prompt-overrides.js +0 -69
- package/dist/src/lib/terminal-capabilities.js +0 -51
- package/dist/src/lib/user-agent.js +0 -29
- package/dist/src/output/format.js +0 -110
- package/dist/src/print/command.js +0 -221
- package/dist/src/print/validate/tui/ValidateView.js +0 -37
- package/dist/src/print/validate/validators/cdf-validator.js +0 -104
- package/dist/src/print/validate/validators/dtcg-validator.js +0 -110
- package/dist/src/print/validate/validators/format-errors.js +0 -18
- package/dist/src/program.js +0 -105
- package/dist/src/runs/export-helpers.js +0 -59
- package/dist/src/runs/fingerprint.js +0 -75
- package/dist/src/runs/ls-command.js +0 -137
- package/dist/src/runs/modify-launcher.js +0 -42
- package/dist/src/runs/path-prompt.js +0 -72
- package/dist/src/runs/push-creds-prompt.js +0 -39
- package/dist/src/runs/push-helpers.js +0 -72
- package/dist/src/runs/push-launcher.js +0 -77
- package/dist/src/runs/replay-helpers.js +0 -164
- package/dist/src/runs/resolve-run-target.js +0 -45
- package/dist/src/runs/run-picker-mount.js +0 -72
- package/dist/src/runs/run-picker.js +0 -129
- package/dist/src/runs/save-conflict.js +0 -56
- package/dist/src/runs/save-path-resolver.js +0 -75
- package/dist/src/runs/staleness.js +0 -140
- package/dist/src/runs/store.js +0 -136
- package/dist/src/session/cache-keys.js +0 -44
- package/dist/src/session/command.js +0 -261
- package/dist/src/session/db.js +0 -1538
- package/dist/src/session/migration.js +0 -117
- package/dist/src/session/session-id.js +0 -212
- package/dist/src/session/stats.js +0 -89
- package/dist/src/setup/analytics-prompt.js +0 -24
- package/dist/src/setup/auto-filter-prompt.js +0 -24
- package/dist/src/setup/command.js +0 -966
- package/dist/src/setup/composition-mode-prompt.js +0 -37
- package/dist/src/setup/debug-mode-prompt.js +0 -22
- package/dist/src/types.js +0 -1
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { sha256Hex } from './fingerprint.js';
|
|
4
|
-
const UNKNOWN = {
|
|
5
|
-
stale: false,
|
|
6
|
-
staleComponents: [],
|
|
7
|
-
staleTokens: false,
|
|
8
|
-
savedComponentsEdited: false,
|
|
9
|
-
savedTokensEdited: false,
|
|
10
|
-
missingSourceFiles: [],
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* Re-stat the source files and re-hash the saved artifacts captured by a
|
|
14
|
-
* run record's fingerprints, and report what (if anything) drifted.
|
|
15
|
-
*
|
|
16
|
-
* Returns `stale: false` when the run is missing a source fingerprint
|
|
17
|
-
* entirely (v2 records pre-dating runs.json v3 — treat as UNKNOWN, not
|
|
18
|
-
* stale, so existing runs keep replaying).
|
|
19
|
-
*/
|
|
20
|
-
export async function checkRunStaleness(run) {
|
|
21
|
-
if (!run.sourceFingerprint)
|
|
22
|
-
return { ...UNKNOWN };
|
|
23
|
-
const result = {
|
|
24
|
-
stale: false,
|
|
25
|
-
staleComponents: [],
|
|
26
|
-
staleTokens: false,
|
|
27
|
-
savedComponentsEdited: false,
|
|
28
|
-
savedTokensEdited: false,
|
|
29
|
-
missingSourceFiles: [],
|
|
30
|
-
};
|
|
31
|
-
// ── Per-file source check ────────────────────────────────────────────
|
|
32
|
-
for (const [absPath, entry] of Object.entries(run.sourceFingerprint.files)) {
|
|
33
|
-
try {
|
|
34
|
-
const st = await stat(absPath);
|
|
35
|
-
if (st.mtime.toISOString() !== entry.mtime) {
|
|
36
|
-
result.staleComponents.push(entry.componentName ?? absPath);
|
|
37
|
-
result.stale = true;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
result.missingSourceFiles.push(absPath);
|
|
42
|
-
result.stale = true;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// ── Raw tokens (single file: re-stat AND re-hash) ────────────────────
|
|
46
|
-
if (run.sourceFingerprint.rawTokensPath) {
|
|
47
|
-
const absPath = run.sourceFingerprint.rawTokensPath;
|
|
48
|
-
try {
|
|
49
|
-
const [st, buf] = await Promise.all([stat(absPath), readFile(absPath)]);
|
|
50
|
-
const mtime = st.mtime.toISOString();
|
|
51
|
-
const hash = sha256Hex(buf);
|
|
52
|
-
const mtimeChanged = run.sourceFingerprint.rawTokensMtime !== null && mtime !== run.sourceFingerprint.rawTokensMtime;
|
|
53
|
-
const hashChanged = run.sourceFingerprint.rawTokensContentHash !== null && hash !== run.sourceFingerprint.rawTokensContentHash;
|
|
54
|
-
if (mtimeChanged || hashChanged) {
|
|
55
|
-
result.staleTokens = true;
|
|
56
|
-
result.stale = true;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
result.staleTokens = true;
|
|
61
|
-
result.stale = true;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
// ── Saved-file check (read & hash components.json / tokens.json) ─────
|
|
65
|
-
if (run.savedFingerprint) {
|
|
66
|
-
if (run.savedFingerprint.componentsJsonHash !== null) {
|
|
67
|
-
const compPath = join(run.savePath, 'components.json');
|
|
68
|
-
try {
|
|
69
|
-
const buf = await readFile(compPath);
|
|
70
|
-
if (sha256Hex(buf) !== run.savedFingerprint.componentsJsonHash) {
|
|
71
|
-
result.savedComponentsEdited = true;
|
|
72
|
-
result.stale = true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
result.savedComponentsEdited = true;
|
|
77
|
-
result.stale = true;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (run.savedFingerprint.tokensJsonHash !== null) {
|
|
81
|
-
const tokensPath = run.tokensPath ?? join(run.savePath, 'tokens.json');
|
|
82
|
-
try {
|
|
83
|
-
const buf = await readFile(tokensPath);
|
|
84
|
-
if (sha256Hex(buf) !== run.savedFingerprint.tokensJsonHash) {
|
|
85
|
-
result.savedTokensEdited = true;
|
|
86
|
-
result.stale = true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
result.savedTokensEdited = true;
|
|
91
|
-
result.stale = true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Render a short human-readable summary of a Staleness result, e.g.
|
|
99
|
-
* `src: 3, tokens, saved`. Returns the empty string when not stale.
|
|
100
|
-
*/
|
|
101
|
-
export function shortStalenessSummary(s) {
|
|
102
|
-
if (!s.stale)
|
|
103
|
-
return '';
|
|
104
|
-
const parts = [];
|
|
105
|
-
const srcCount = s.staleComponents.length + s.missingSourceFiles.length;
|
|
106
|
-
if (srcCount > 0)
|
|
107
|
-
parts.push(`src: ${srcCount}`);
|
|
108
|
-
if (s.staleTokens)
|
|
109
|
-
parts.push('tokens');
|
|
110
|
-
if (s.savedComponentsEdited || s.savedTokensEdited)
|
|
111
|
-
parts.push('saved');
|
|
112
|
-
return parts.join(', ');
|
|
113
|
-
}
|
|
114
|
-
/**
|
|
115
|
-
* Render a multi-line detail block for staleness output (used by `runs <id>`
|
|
116
|
-
* detail view and replay refusals). Caps long lists at 5 entries.
|
|
117
|
-
*/
|
|
118
|
-
export function formatStalenessDetail(s) {
|
|
119
|
-
if (!s.stale)
|
|
120
|
-
return ['Status: FRESH'];
|
|
121
|
-
const lines = ['Status: STALE'];
|
|
122
|
-
const cap = 5;
|
|
123
|
-
if (s.staleComponents.length > 0) {
|
|
124
|
-
const head = s.staleComponents.slice(0, cap).join(', ');
|
|
125
|
-
const more = s.staleComponents.length > cap ? ` and ${s.staleComponents.length - cap} more` : '';
|
|
126
|
-
lines.push(` Source changed: ${head}${more}`);
|
|
127
|
-
}
|
|
128
|
-
if (s.missingSourceFiles.length > 0) {
|
|
129
|
-
const head = s.missingSourceFiles.slice(0, cap).join(', ');
|
|
130
|
-
const more = s.missingSourceFiles.length > cap ? ` and ${s.missingSourceFiles.length - cap} more` : '';
|
|
131
|
-
lines.push(` Source missing: ${head}${more}`);
|
|
132
|
-
}
|
|
133
|
-
if (s.staleTokens)
|
|
134
|
-
lines.push(' Raw tokens drifted');
|
|
135
|
-
if (s.savedComponentsEdited)
|
|
136
|
-
lines.push(' Saved components.json edited');
|
|
137
|
-
if (s.savedTokensEdited)
|
|
138
|
-
lines.push(' Saved tokens.json edited');
|
|
139
|
-
return lines;
|
|
140
|
-
}
|
package/dist/src/runs/store.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { randomBytes } from 'node:crypto';
|
|
5
|
-
export const RUNS_FILE_VERSION = 3;
|
|
6
|
-
export const RUNS_FILE_CAP = 200;
|
|
7
|
-
/** Versions this CLI can read. v1 / v2 files are auto-migrated in memory;
|
|
8
|
-
* new writes always use the latest version. */
|
|
9
|
-
export const READABLE_VERSIONS = new Set([1, 2, 3]);
|
|
10
|
-
const RUNS_DIR = join(homedir(), '.config', 'experiences');
|
|
11
|
-
const RUNS_PATH = join(RUNS_DIR, 'runs.json');
|
|
12
|
-
export function runsFilePath() {
|
|
13
|
-
return RUNS_PATH;
|
|
14
|
-
}
|
|
15
|
-
// Crockford-base32 ULID (26 chars: 10 timestamp + 16 random).
|
|
16
|
-
const CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
|
17
|
-
function encodeBase32(bytes, length) {
|
|
18
|
-
let out = '';
|
|
19
|
-
let bits = 0;
|
|
20
|
-
let value = 0;
|
|
21
|
-
for (const b of bytes) {
|
|
22
|
-
value = (value << 8) | b;
|
|
23
|
-
bits += 8;
|
|
24
|
-
while (bits >= 5) {
|
|
25
|
-
bits -= 5;
|
|
26
|
-
out += CROCKFORD[(value >> bits) & 0x1f];
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (bits > 0)
|
|
30
|
-
out += CROCKFORD[(value << (5 - bits)) & 0x1f];
|
|
31
|
-
return out.slice(0, length);
|
|
32
|
-
}
|
|
33
|
-
export function generateUlid(now = Date.now()) {
|
|
34
|
-
// 48-bit timestamp -> 10 chars
|
|
35
|
-
const tsBytes = new Uint8Array(6);
|
|
36
|
-
let n = now;
|
|
37
|
-
for (let i = 5; i >= 0; i--) {
|
|
38
|
-
tsBytes[i] = n & 0xff;
|
|
39
|
-
n = Math.floor(n / 256);
|
|
40
|
-
}
|
|
41
|
-
const ts = encodeBase32(tsBytes, 10);
|
|
42
|
-
const rand = encodeBase32(randomBytes(10), 16);
|
|
43
|
-
return (ts + rand).toUpperCase();
|
|
44
|
-
}
|
|
45
|
-
function migrateRecord(rec) {
|
|
46
|
-
return {
|
|
47
|
-
...rec,
|
|
48
|
-
tokensPath: rec.tokensPath ?? null,
|
|
49
|
-
tokenSessionId: rec.tokenSessionId ?? null,
|
|
50
|
-
sourceFingerprint: rec.sourceFingerprint ?? null,
|
|
51
|
-
savedFingerprint: rec.savedFingerprint ?? null,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
async function readFileMaybe() {
|
|
55
|
-
try {
|
|
56
|
-
const raw = await readFile(RUNS_PATH, 'utf8');
|
|
57
|
-
const parsed = JSON.parse(raw);
|
|
58
|
-
if (!READABLE_VERSIONS.has(parsed.version)) {
|
|
59
|
-
throw new Error(`runs.json version mismatch: file is v${parsed.version}, this CLI expects v${RUNS_FILE_VERSION} (also reads: ${[...READABLE_VERSIONS].join(', ')}). Back up and remove the file to start fresh.`);
|
|
60
|
-
}
|
|
61
|
-
// Always migrate to current schema in memory; new writes will persist v2.
|
|
62
|
-
const runs = parsed.runs.map(migrateRecord);
|
|
63
|
-
return { version: RUNS_FILE_VERSION, runs };
|
|
64
|
-
}
|
|
65
|
-
catch (err) {
|
|
66
|
-
const code = err.code;
|
|
67
|
-
if (code === 'ENOENT')
|
|
68
|
-
return null;
|
|
69
|
-
throw err;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async function writeAtomic(file) {
|
|
73
|
-
await mkdir(RUNS_DIR, { recursive: true });
|
|
74
|
-
const body = JSON.stringify(file, null, 2) + '\n';
|
|
75
|
-
const tmp = `${RUNS_PATH}.${process.pid}.${Date.now()}.tmp`;
|
|
76
|
-
await writeFile(tmp, body, { mode: 0o600 });
|
|
77
|
-
await rename(tmp, RUNS_PATH);
|
|
78
|
-
}
|
|
79
|
-
export async function appendRun(input) {
|
|
80
|
-
const existing = await readFileMaybe();
|
|
81
|
-
const record = {
|
|
82
|
-
...input,
|
|
83
|
-
id: input.id ?? generateUlid(),
|
|
84
|
-
createdAt: input.createdAt ?? new Date().toISOString(),
|
|
85
|
-
sourceFingerprint: input.sourceFingerprint ?? null,
|
|
86
|
-
savedFingerprint: input.savedFingerprint ?? null,
|
|
87
|
-
};
|
|
88
|
-
const runs = existing ? [record, ...existing.runs] : [record];
|
|
89
|
-
if (runs.length > RUNS_FILE_CAP) {
|
|
90
|
-
const dropped = runs.length - RUNS_FILE_CAP;
|
|
91
|
-
runs.length = RUNS_FILE_CAP;
|
|
92
|
-
console.warn(`runs.json reached cap of ${RUNS_FILE_CAP}; dropped ${dropped} oldest entr${dropped === 1 ? 'y' : 'ies'}.`);
|
|
93
|
-
}
|
|
94
|
-
await writeAtomic({ version: RUNS_FILE_VERSION, runs });
|
|
95
|
-
return record;
|
|
96
|
-
}
|
|
97
|
-
export async function listRuns(opts = {}) {
|
|
98
|
-
const file = await readFileMaybe();
|
|
99
|
-
if (!file)
|
|
100
|
-
return [];
|
|
101
|
-
let runs = file.runs;
|
|
102
|
-
if (opts.projectPath)
|
|
103
|
-
runs = runs.filter((r) => r.projectPath === opts.projectPath);
|
|
104
|
-
if (opts.before)
|
|
105
|
-
runs = runs.filter((r) => r.createdAt < opts.before);
|
|
106
|
-
if (opts.after)
|
|
107
|
-
runs = runs.filter((r) => r.createdAt > opts.after);
|
|
108
|
-
if (typeof opts.limit === 'number')
|
|
109
|
-
runs = runs.slice(0, opts.limit);
|
|
110
|
-
return runs;
|
|
111
|
-
}
|
|
112
|
-
export async function getRun(id) {
|
|
113
|
-
const file = await readFileMaybe();
|
|
114
|
-
const found = file?.runs.find((r) => r.id === id);
|
|
115
|
-
if (!found)
|
|
116
|
-
throw new Error(`Run ${id} not found in ${RUNS_PATH}`);
|
|
117
|
-
return found;
|
|
118
|
-
}
|
|
119
|
-
export async function updateRun(id, patch) {
|
|
120
|
-
const file = (await readFileMaybe()) ?? { version: RUNS_FILE_VERSION, runs: [] };
|
|
121
|
-
const idx = file.runs.findIndex((r) => r.id === id);
|
|
122
|
-
if (idx < 0)
|
|
123
|
-
throw new Error(`Run ${id} not found in ${RUNS_PATH}`);
|
|
124
|
-
const updated = { ...file.runs[idx], ...patch, id };
|
|
125
|
-
file.runs[idx] = updated;
|
|
126
|
-
await writeAtomic(file);
|
|
127
|
-
return updated;
|
|
128
|
-
}
|
|
129
|
-
export async function findRunBySavePath(savePath) {
|
|
130
|
-
const file = await readFileMaybe();
|
|
131
|
-
return file?.runs.find((r) => r.savePath === savePath) ?? null;
|
|
132
|
-
}
|
|
133
|
-
export async function findAllRunsBySavePath(savePath) {
|
|
134
|
-
const file = await readFileMaybe();
|
|
135
|
-
return file?.runs.filter((r) => r.savePath === savePath) ?? [];
|
|
136
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hash helpers for the fine-grained LLM cache.
|
|
3
|
-
*
|
|
4
|
-
* Three layers of cache (extract, select, generate) all need stable content
|
|
5
|
-
* hashes for cache-key derivation. Keep these primitives in one place so the
|
|
6
|
-
* algorithm and encoding never drift between callers.
|
|
7
|
-
*
|
|
8
|
-
* All hashes are sha256 hex (64 chars). No npm deps — uses node:crypto.
|
|
9
|
-
*/
|
|
10
|
-
import { createHash } from 'node:crypto';
|
|
11
|
-
import { readFile } from 'node:fs/promises';
|
|
12
|
-
import { resolve } from 'node:path';
|
|
13
|
-
import { resolveSkillPath } from '@contentful/experience-design-system-generation';
|
|
14
|
-
/** sha256 hex of a string. Stable across runs. */
|
|
15
|
-
export function hashContent(content) {
|
|
16
|
-
return createHash('sha256').update(content).digest('hex');
|
|
17
|
-
}
|
|
18
|
-
/** sha256 hex of a file's UTF-8 content. */
|
|
19
|
-
export async function hashFile(filePath) {
|
|
20
|
-
const content = await readFile(filePath, 'utf8');
|
|
21
|
-
return hashContent(content);
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Hash the prompt content for a skill — either the bundled skill file shipped
|
|
25
|
-
* with the CLI, or a custom override path. Used to key the select_cache and
|
|
26
|
-
* generation_cache so cache entries invalidate when the prompt itself changes
|
|
27
|
-
* (including bundled-skill updates across CLI versions).
|
|
28
|
-
*
|
|
29
|
-
* Also folds in the resolved agent and model: a cache hit must not stand in
|
|
30
|
-
* for an agent run that was never made with the currently requested
|
|
31
|
-
* agent/model, or `--agent`/`--model` overrides would appear to be ignored
|
|
32
|
-
* whenever a prior cache entry already exists for the same input/prompt.
|
|
33
|
-
*
|
|
34
|
-
* @param skill — which skill (matches resolveSkillPath in prompt-builder)
|
|
35
|
-
* @param agent — resolved agent name for this run
|
|
36
|
-
* @param model — resolved model for this run (undefined resolves to the agent's default)
|
|
37
|
-
* @param skillPathOverride — optional absolute/relative path to a custom prompt
|
|
38
|
-
*/
|
|
39
|
-
export async function hashPromptForSkill(skill, agent, model, skillPathOverride) {
|
|
40
|
-
const promptHash = skillPathOverride
|
|
41
|
-
? await hashFile(resolve(skillPathOverride))
|
|
42
|
-
: await hashFile(resolveSkillPath(skill));
|
|
43
|
-
return hashContent(`${promptHash}|${agent}|${model ?? ''}`);
|
|
44
|
-
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { openPipelineDb, getPipelineDbPath } from './db.js';
|
|
2
|
-
import { getStats, formatStatsText } from './stats.js';
|
|
3
|
-
import { runMigrationIfNeeded } from './migration.js';
|
|
4
|
-
function parseDuration(str) {
|
|
5
|
-
const m = /^(\d+)([dwmy])$/.exec(str);
|
|
6
|
-
if (!m)
|
|
7
|
-
return null;
|
|
8
|
-
const n = parseInt(m[1], 10);
|
|
9
|
-
const unit = m[2];
|
|
10
|
-
const days = unit === 'd' ? n : unit === 'w' ? n * 7 : unit === 'm' ? n * 30 : n * 365;
|
|
11
|
-
return days * 24 * 60 * 60 * 1000;
|
|
12
|
-
}
|
|
13
|
-
function formatDate(iso) {
|
|
14
|
-
return iso.replace('T', ' ').slice(0, 16);
|
|
15
|
-
}
|
|
16
|
-
export function registerSessionCommand(program) {
|
|
17
|
-
const session = program.command('session').description('Manage pipeline sessions');
|
|
18
|
-
// session list
|
|
19
|
-
session
|
|
20
|
-
.command('list')
|
|
21
|
-
.description('List all pipeline sessions')
|
|
22
|
-
.option('--status <status>', 'Filter by status: in-progress, complete, failed, interrupted')
|
|
23
|
-
.option('--all', 'Include interrupted sessions (hidden by default)')
|
|
24
|
-
.option('--limit <n>', 'Max rows to return', '20')
|
|
25
|
-
.option('--json', 'Force JSON output')
|
|
26
|
-
.action((opts) => {
|
|
27
|
-
const dbPath = getPipelineDbPath();
|
|
28
|
-
const db = openPipelineDb(dbPath);
|
|
29
|
-
runMigrationIfNeeded(db);
|
|
30
|
-
const limit = parseInt(opts.limit, 10) || 20;
|
|
31
|
-
const rows = db
|
|
32
|
-
.prepare(`SELECT s.id, s.name, s.updated_at,
|
|
33
|
-
COUNT(st.id) AS step_count,
|
|
34
|
-
(SELECT command FROM steps WHERE session_id = s.id ORDER BY started_at DESC, id DESC LIMIT 1) AS last_step,
|
|
35
|
-
(SELECT status FROM steps WHERE session_id = s.id ORDER BY started_at DESC, id DESC LIMIT 1) AS last_status
|
|
36
|
-
FROM sessions s
|
|
37
|
-
LEFT JOIN steps st ON st.session_id = s.id
|
|
38
|
-
GROUP BY s.id
|
|
39
|
-
ORDER BY s.updated_at DESC
|
|
40
|
-
LIMIT ?`)
|
|
41
|
-
.all(limit);
|
|
42
|
-
let filtered = rows;
|
|
43
|
-
if (opts.status) {
|
|
44
|
-
const filterStatus = opts.status === 'in-progress' ? 'pending' : opts.status;
|
|
45
|
-
filtered = rows.filter((r) => r.last_status === filterStatus || (!r.last_status && opts.status === 'in-progress'));
|
|
46
|
-
}
|
|
47
|
-
else if (!opts.all) {
|
|
48
|
-
// Hide interrupted sessions by default — they're typically migration artifacts
|
|
49
|
-
filtered = rows.filter((r) => r.last_status !== 'interrupted');
|
|
50
|
-
}
|
|
51
|
-
db.close();
|
|
52
|
-
if (opts.json) {
|
|
53
|
-
process.stdout.write(JSON.stringify(filtered.map((r) => ({
|
|
54
|
-
id: r.id,
|
|
55
|
-
name: r.name,
|
|
56
|
-
status: r.last_status ?? null,
|
|
57
|
-
stepCount: r.step_count,
|
|
58
|
-
lastStep: r.last_step,
|
|
59
|
-
updatedAt: r.updated_at,
|
|
60
|
-
})), null, 2) + '\n');
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (filtered.length === 0) {
|
|
64
|
-
process.stdout.write('No sessions found.\n');
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const header = 'ID Name Steps Status Last Step Updated';
|
|
68
|
-
process.stdout.write(header + '\n');
|
|
69
|
-
process.stdout.write('─'.repeat(header.length) + '\n');
|
|
70
|
-
for (const r of filtered) {
|
|
71
|
-
const id = r.id.padEnd(22);
|
|
72
|
-
const name = (r.name ?? '(none)').slice(0, 18).padEnd(20);
|
|
73
|
-
const steps = String(r.step_count).padStart(5);
|
|
74
|
-
const status = (r.last_status ?? '—').padEnd(13);
|
|
75
|
-
const lastStep = (r.last_step ?? '—').padEnd(22);
|
|
76
|
-
const updated = formatDate(r.updated_at);
|
|
77
|
-
process.stdout.write(`${id}${name}${steps} ${status}${lastStep}${updated}\n`);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
// session show
|
|
81
|
-
session
|
|
82
|
-
.command('show <id>')
|
|
83
|
-
.description('Show all steps for a session')
|
|
84
|
-
.option('--json', 'Force JSON output')
|
|
85
|
-
.action((id, opts) => {
|
|
86
|
-
const dbPath = getPipelineDbPath();
|
|
87
|
-
const db = openPipelineDb(dbPath);
|
|
88
|
-
runMigrationIfNeeded(db);
|
|
89
|
-
const sess = db.prepare('SELECT id, name, created_at, updated_at FROM sessions WHERE id = ?').get(id);
|
|
90
|
-
if (!sess) {
|
|
91
|
-
db.close();
|
|
92
|
-
process.stderr.write(`Error: session '${id}' not found.\n`);
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
const steps = db
|
|
96
|
-
.prepare(`SELECT id, command, status, started_at, completed_at, inputs, outputs, error
|
|
97
|
-
FROM steps WHERE session_id = ? ORDER BY started_at ASC, id ASC`)
|
|
98
|
-
.all(id);
|
|
99
|
-
db.close();
|
|
100
|
-
if (opts.json) {
|
|
101
|
-
process.stdout.write(JSON.stringify({
|
|
102
|
-
id: sess.id,
|
|
103
|
-
name: sess.name,
|
|
104
|
-
createdAt: sess.created_at,
|
|
105
|
-
updatedAt: sess.updated_at,
|
|
106
|
-
steps: steps.map((s, i) => ({
|
|
107
|
-
number: i + 1,
|
|
108
|
-
command: s.command,
|
|
109
|
-
status: s.status,
|
|
110
|
-
startedAt: s.started_at,
|
|
111
|
-
completedAt: s.completed_at,
|
|
112
|
-
inputs: JSON.parse(s.inputs),
|
|
113
|
-
outputs: JSON.parse(s.outputs),
|
|
114
|
-
error: s.error,
|
|
115
|
-
})),
|
|
116
|
-
}, null, 2) + '\n');
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
process.stdout.write(`Session: ${sess.id}\n`);
|
|
120
|
-
process.stdout.write(`Name: ${sess.name ?? '(none)'}\n`);
|
|
121
|
-
process.stdout.write(`Created: ${formatDate(sess.created_at)}\n`);
|
|
122
|
-
process.stdout.write(`Updated: ${formatDate(sess.updated_at)}\n`);
|
|
123
|
-
process.stdout.write(`\nSteps\n${'─'.repeat(60)}\n`);
|
|
124
|
-
for (const [i, s] of steps.entries()) {
|
|
125
|
-
const num = String(i + 1).padStart(3);
|
|
126
|
-
const cmd = s.command.padEnd(20);
|
|
127
|
-
const stat = s.status.padEnd(12);
|
|
128
|
-
const timing = s.completed_at
|
|
129
|
-
? `${s.started_at.slice(11, 16)} → ${s.completed_at.slice(11, 16)}`
|
|
130
|
-
: `${s.started_at.slice(11, 16)} → …`;
|
|
131
|
-
let inputs = {};
|
|
132
|
-
let outputs = {};
|
|
133
|
-
try {
|
|
134
|
-
inputs = JSON.parse(s.inputs);
|
|
135
|
-
outputs = JSON.parse(s.outputs);
|
|
136
|
-
}
|
|
137
|
-
catch {
|
|
138
|
-
// ignore
|
|
139
|
-
}
|
|
140
|
-
const inputStr = Object.values(inputs).join(', ');
|
|
141
|
-
const outputStr = Object.values(outputs).join(', ');
|
|
142
|
-
const paths = [inputStr, outputStr].filter(Boolean).join(' → ');
|
|
143
|
-
process.stdout.write(`${num} ${cmd}${stat}${timing} ${paths}\n`);
|
|
144
|
-
if (s.error) {
|
|
145
|
-
process.stdout.write(` Error: ${s.error}\n`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
// session stats
|
|
150
|
-
session
|
|
151
|
-
.command('stats')
|
|
152
|
-
.description('Show aggregate storage and record counts')
|
|
153
|
-
.option('--json', 'Force JSON output')
|
|
154
|
-
.action((opts) => {
|
|
155
|
-
const dbPath = getPipelineDbPath();
|
|
156
|
-
const db = openPipelineDb(dbPath);
|
|
157
|
-
runMigrationIfNeeded(db);
|
|
158
|
-
const stats = getStats(db, dbPath);
|
|
159
|
-
db.close();
|
|
160
|
-
if (opts.json) {
|
|
161
|
-
process.stdout.write(JSON.stringify(stats, null, 2) + '\n');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
process.stdout.write(formatStatsText(stats) + '\n');
|
|
165
|
-
});
|
|
166
|
-
// session prune
|
|
167
|
-
session
|
|
168
|
-
.command('prune')
|
|
169
|
-
.description('Delete sessions matching criteria')
|
|
170
|
-
.option('--id <id>', 'Delete a specific session by ID')
|
|
171
|
-
.option('--older-than <duration>', 'Delete sessions older than this age (e.g. 30d, 2w, 1y)')
|
|
172
|
-
.option('--status <status>', 'Delete sessions by last step status: complete, failed, interrupted')
|
|
173
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
174
|
-
.option('--dry-run', 'Print what would be deleted without deleting')
|
|
175
|
-
.action(async (opts) => {
|
|
176
|
-
if (!opts.id && !opts.olderThan && !opts.status) {
|
|
177
|
-
process.stderr.write('Error: at least one of --id, --older-than, or --status is required\n');
|
|
178
|
-
process.exit(1);
|
|
179
|
-
}
|
|
180
|
-
const dbPath = getPipelineDbPath();
|
|
181
|
-
const db = openPipelineDb(dbPath);
|
|
182
|
-
runMigrationIfNeeded(db);
|
|
183
|
-
// Build the list of sessions to delete
|
|
184
|
-
let candidates;
|
|
185
|
-
if (opts.id) {
|
|
186
|
-
const sess = db.prepare('SELECT id, updated_at FROM sessions WHERE id = ?').get(opts.id);
|
|
187
|
-
if (!sess) {
|
|
188
|
-
db.close();
|
|
189
|
-
process.stderr.write(`Error: session '${opts.id}' not found.\n`);
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
candidates = [sess];
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
candidates = db.prepare('SELECT id, updated_at FROM sessions ORDER BY updated_at DESC').all();
|
|
196
|
-
}
|
|
197
|
-
if (opts.olderThan) {
|
|
198
|
-
const ms = parseDuration(opts.olderThan);
|
|
199
|
-
if (!ms) {
|
|
200
|
-
db.close();
|
|
201
|
-
process.stderr.write(`Error: invalid duration '${opts.olderThan}'. Use e.g. 30d, 2w, 1y.\n`);
|
|
202
|
-
process.exit(1);
|
|
203
|
-
}
|
|
204
|
-
const cutoff = new Date(Date.now() - ms).toISOString();
|
|
205
|
-
candidates = candidates.filter((c) => c.updated_at < cutoff);
|
|
206
|
-
}
|
|
207
|
-
if (opts.status) {
|
|
208
|
-
const filterStatus = opts.status === 'in-progress' ? 'pending' : opts.status;
|
|
209
|
-
candidates = candidates.filter((c) => {
|
|
210
|
-
const lastStep = db
|
|
211
|
-
.prepare('SELECT status FROM steps WHERE session_id = ? ORDER BY started_at DESC, id DESC LIMIT 1')
|
|
212
|
-
.get(c.id);
|
|
213
|
-
return lastStep?.status === filterStatus;
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
if (candidates.length === 0) {
|
|
217
|
-
db.close();
|
|
218
|
-
process.stdout.write('No sessions match the specified criteria.\n');
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (opts.dryRun) {
|
|
222
|
-
db.close();
|
|
223
|
-
process.stdout.write(`Would delete ${candidates.length} session(s):\n\n`);
|
|
224
|
-
for (const c of candidates) {
|
|
225
|
-
process.stdout.write(` ${c.id.padEnd(22)}last updated ${c.updated_at.slice(0, 10)}\n`);
|
|
226
|
-
}
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (!opts.yes && process.stdout.isTTY) {
|
|
230
|
-
process.stdout.write(`This will delete ${candidates.length} session(s) matching your criteria.\n\n`);
|
|
231
|
-
for (const c of candidates) {
|
|
232
|
-
process.stdout.write(` ${c.id.padEnd(22)}last updated ${c.updated_at.slice(0, 10)}\n`);
|
|
233
|
-
}
|
|
234
|
-
process.stdout.write('\nDelete these sessions? (y/N) ');
|
|
235
|
-
const answer = await new Promise((resolve) => {
|
|
236
|
-
process.stdin.setEncoding('utf8');
|
|
237
|
-
process.stdin.once('data', (chunk) => resolve(String(chunk).trim().toLowerCase()));
|
|
238
|
-
});
|
|
239
|
-
if (answer !== 'y') {
|
|
240
|
-
db.close();
|
|
241
|
-
process.stdout.write('Cancelled.\n');
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
db.exec('BEGIN');
|
|
246
|
-
try {
|
|
247
|
-
for (const c of candidates) {
|
|
248
|
-
db.prepare('DELETE FROM sessions WHERE id = ?').run(c.id);
|
|
249
|
-
}
|
|
250
|
-
db.exec('COMMIT');
|
|
251
|
-
}
|
|
252
|
-
catch (e) {
|
|
253
|
-
db.exec('ROLLBACK');
|
|
254
|
-
db.close();
|
|
255
|
-
process.stderr.write(`Error: failed to delete sessions: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|
|
258
|
-
db.close();
|
|
259
|
-
process.stdout.write(`Deleted ${candidates.length} session(s).\n`);
|
|
260
|
-
});
|
|
261
|
-
}
|