@stackbilt/cli 0.15.1 → 1.0.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.
Files changed (114) hide show
  1. package/dist/__tests__/adf-init.test.js +31 -0
  2. package/dist/__tests__/adf-init.test.js.map +1 -1
  3. package/dist/__tests__/adf-patch.test.d.ts +2 -0
  4. package/dist/__tests__/adf-patch.test.d.ts.map +1 -0
  5. package/dist/__tests__/adf-patch.test.js +192 -0
  6. package/dist/__tests__/adf-patch.test.js.map +1 -0
  7. package/dist/__tests__/bootstrap.test.js +107 -0
  8. package/dist/__tests__/bootstrap.test.js.map +1 -1
  9. package/dist/__tests__/context-refresh-repo-intel.test.d.ts +9 -0
  10. package/dist/__tests__/context-refresh-repo-intel.test.d.ts.map +1 -0
  11. package/dist/__tests__/context-refresh-repo-intel.test.js +228 -0
  12. package/dist/__tests__/context-refresh-repo-intel.test.js.map +1 -0
  13. package/dist/__tests__/context-refresh.test.d.ts +2 -0
  14. package/dist/__tests__/context-refresh.test.d.ts.map +1 -0
  15. package/dist/__tests__/context-refresh.test.js +198 -0
  16. package/dist/__tests__/context-refresh.test.js.map +1 -0
  17. package/dist/__tests__/context.test.js +62 -0
  18. package/dist/__tests__/context.test.js.map +1 -1
  19. package/dist/__tests__/hook.test.js +25 -0
  20. package/dist/__tests__/hook.test.js.map +1 -1
  21. package/dist/__tests__/score.test.js +58 -0
  22. package/dist/__tests__/score.test.js.map +1 -1
  23. package/dist/__tests__/serve-context.test.d.ts +2 -0
  24. package/dist/__tests__/serve-context.test.d.ts.map +1 -0
  25. package/dist/__tests__/serve-context.test.js +112 -0
  26. package/dist/__tests__/serve-context.test.js.map +1 -0
  27. package/dist/__tests__/setup.test.js +48 -0
  28. package/dist/__tests__/setup.test.js.map +1 -1
  29. package/dist/__tests__/why.test.d.ts +2 -0
  30. package/dist/__tests__/why.test.d.ts.map +1 -0
  31. package/dist/__tests__/why.test.js +145 -0
  32. package/dist/__tests__/why.test.js.map +1 -0
  33. package/dist/commands/adf.d.ts +2 -2
  34. package/dist/commands/adf.d.ts.map +1 -1
  35. package/dist/commands/adf.js +94 -1
  36. package/dist/commands/adf.js.map +1 -1
  37. package/dist/commands/bootstrap.d.ts.map +1 -1
  38. package/dist/commands/bootstrap.js +145 -34
  39. package/dist/commands/bootstrap.js.map +1 -1
  40. package/dist/commands/context-refresh.d.ts +16 -0
  41. package/dist/commands/context-refresh.d.ts.map +1 -0
  42. package/dist/commands/context-refresh.js +841 -0
  43. package/dist/commands/context-refresh.js.map +1 -0
  44. package/dist/commands/context.d.ts.map +1 -1
  45. package/dist/commands/context.js +104 -7
  46. package/dist/commands/context.js.map +1 -1
  47. package/dist/commands/hook.d.ts +1 -0
  48. package/dist/commands/hook.d.ts.map +1 -1
  49. package/dist/commands/hook.js +30 -1
  50. package/dist/commands/hook.js.map +1 -1
  51. package/dist/commands/score.js +76 -14
  52. package/dist/commands/score.js.map +1 -1
  53. package/dist/commands/serve.d.ts +22 -1
  54. package/dist/commands/serve.d.ts.map +1 -1
  55. package/dist/commands/serve.js +206 -7
  56. package/dist/commands/serve.js.map +1 -1
  57. package/dist/commands/setup.d.ts.map +1 -1
  58. package/dist/commands/setup.js +47 -1
  59. package/dist/commands/setup.js.map +1 -1
  60. package/dist/commands/why.d.ts.map +1 -1
  61. package/dist/commands/why.js +44 -0
  62. package/dist/commands/why.js.map +1 -1
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +10 -28
  65. package/dist/index.js.map +1 -1
  66. package/package.json +27 -15
  67. package/dist/__tests__/auth-wiring.test.d.ts +0 -2
  68. package/dist/__tests__/auth-wiring.test.d.ts.map +0 -1
  69. package/dist/__tests__/auth-wiring.test.js +0 -158
  70. package/dist/__tests__/auth-wiring.test.js.map +0 -1
  71. package/dist/__tests__/credentials.test.d.ts +0 -2
  72. package/dist/__tests__/credentials.test.d.ts.map +0 -1
  73. package/dist/__tests__/credentials.test.js +0 -145
  74. package/dist/__tests__/credentials.test.js.map +0 -1
  75. package/dist/__tests__/deprecated-commands.test.d.ts +0 -2
  76. package/dist/__tests__/deprecated-commands.test.d.ts.map +0 -1
  77. package/dist/__tests__/deprecated-commands.test.js +0 -59
  78. package/dist/__tests__/deprecated-commands.test.js.map +0 -1
  79. package/dist/__tests__/login.test.d.ts +0 -2
  80. package/dist/__tests__/login.test.d.ts.map +0 -1
  81. package/dist/__tests__/login.test.js +0 -66
  82. package/dist/__tests__/login.test.js.map +0 -1
  83. package/dist/commands/architect.d.ts +0 -12
  84. package/dist/commands/architect.d.ts.map +0 -1
  85. package/dist/commands/architect.js +0 -167
  86. package/dist/commands/architect.js.map +0 -1
  87. package/dist/commands/deprecation-warning.d.ts +0 -4
  88. package/dist/commands/deprecation-warning.d.ts.map +0 -1
  89. package/dist/commands/deprecation-warning.js +0 -20
  90. package/dist/commands/deprecation-warning.js.map +0 -1
  91. package/dist/commands/login.d.ts +0 -13
  92. package/dist/commands/login.d.ts.map +0 -1
  93. package/dist/commands/login.js +0 -80
  94. package/dist/commands/login.js.map +0 -1
  95. package/dist/commands/run.d.ts +0 -18
  96. package/dist/commands/run.d.ts.map +0 -1
  97. package/dist/commands/run.js +0 -247
  98. package/dist/commands/run.js.map +0 -1
  99. package/dist/commands/scaffold.d.ts +0 -11
  100. package/dist/commands/scaffold.d.ts.map +0 -1
  101. package/dist/commands/scaffold.js +0 -112
  102. package/dist/commands/scaffold.js.map +0 -1
  103. package/dist/credentials.d.ts +0 -34
  104. package/dist/credentials.d.ts.map +0 -1
  105. package/dist/credentials.js +0 -108
  106. package/dist/credentials.js.map +0 -1
  107. package/dist/http-client.d.ts +0 -114
  108. package/dist/http-client.d.ts.map +0 -1
  109. package/dist/http-client.js +0 -69
  110. package/dist/http-client.js.map +0 -1
  111. package/dist/types/scaffold-contract-types.d.ts +0 -90
  112. package/dist/types/scaffold-contract-types.d.ts.map +0 -1
  113. package/dist/types/scaffold-contract-types.js +0 -22
  114. package/dist/types/scaffold-contract-types.js.map +0 -1
@@ -0,0 +1,841 @@
1
+ "use strict";
2
+ /**
3
+ * charter context-refresh
4
+ *
5
+ * Generates a live session snapshot and writes:
6
+ * - .ai/context.adf
7
+ * - .ai/context.snapshot.json
8
+ * Optional:
9
+ * - markdown mirror via --output
10
+ */
11
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ var desc = Object.getOwnPropertyDescriptor(m, k);
14
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
15
+ desc = { enumerable: true, get: function() { return m[k]; } };
16
+ }
17
+ Object.defineProperty(o, k2, desc);
18
+ }) : (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ o[k2] = m[k];
21
+ }));
22
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
23
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
24
+ }) : function(o, v) {
25
+ o["default"] = v;
26
+ });
27
+ var __importStar = (this && this.__importStar) || (function () {
28
+ var ownKeys = function(o) {
29
+ ownKeys = Object.getOwnPropertyNames || function (o) {
30
+ var ar = [];
31
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
32
+ return ar;
33
+ };
34
+ return ownKeys(o);
35
+ };
36
+ return function (mod) {
37
+ if (mod && mod.__esModule) return mod;
38
+ var result = {};
39
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
40
+ __setModuleDefault(result, mod);
41
+ return result;
42
+ };
43
+ })();
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.contextRefreshCommand = contextRefreshCommand;
46
+ const fs = __importStar(require("node:fs"));
47
+ const path = __importStar(require("node:path"));
48
+ const node_child_process_1 = require("node:child_process");
49
+ const index_1 = require("../index");
50
+ const flags_1 = require("../flags");
51
+ const SOURCE_SET = new Set(['git', 'github', 'repo-intel']);
52
+ const DEFAULT_CONFIG = {
53
+ version: 1,
54
+ defaults: {
55
+ sources: ['git'],
56
+ ttlMinutes: 30,
57
+ maxItems: {
58
+ gitCommits: 10,
59
+ gitDirtyFiles: 25,
60
+ githubIssues: 20,
61
+ },
62
+ },
63
+ sources: {
64
+ git: {
65
+ enabled: true,
66
+ },
67
+ github: {
68
+ enabled: false,
69
+ repo: null,
70
+ labels: [],
71
+ includePullRequests: true,
72
+ includeChecks: true,
73
+ },
74
+ 'repo-intel': {
75
+ enabled: true,
76
+ },
77
+ },
78
+ };
79
+ async function contextRefreshCommand(options, args, io) {
80
+ const log = io?.log ?? console.log;
81
+ const resolved = resolveOptions(options, args);
82
+ const snapshotPath = path.join(resolved.aiDirAbs, 'context.snapshot.json');
83
+ const contextAdfPath = path.join(resolved.aiDirAbs, 'context.adf');
84
+ if (resolved.once && !resolved.force) {
85
+ const skipReason = shouldSkipRefresh(snapshotPath, resolved.ttlMinutes);
86
+ if (skipReason.skip) {
87
+ const existing = skipReason.snapshot;
88
+ const files = {
89
+ contextAdf: path.relative(process.cwd(), contextAdfPath) || '.',
90
+ snapshotJson: path.relative(process.cwd(), snapshotPath) || '.',
91
+ outputMarkdown: resolved.outputPathAbs
92
+ ? (path.relative(process.cwd(), resolved.outputPathAbs) || '.')
93
+ : null,
94
+ };
95
+ if (options.format === 'json') {
96
+ log(JSON.stringify({
97
+ status: 'skipped',
98
+ reason: 'fresh_snapshot',
99
+ generatedAt: existing?.generatedAt ?? null,
100
+ expiresAt: existing?.expiresAt ?? null,
101
+ sourcesRequested: resolved.sourcesRequested,
102
+ sourcesUsed: existing?.sourcesUsed ?? [],
103
+ files,
104
+ warnings: [],
105
+ errors: [],
106
+ }, null, 2));
107
+ }
108
+ else {
109
+ log('');
110
+ log(' charter context-refresh');
111
+ log(' Status: skipped (fresh snapshot)');
112
+ log(` Snapshot: ${files.snapshotJson}`);
113
+ log(` TTL (mins): ${resolved.ttlMinutes}`);
114
+ log('');
115
+ }
116
+ return index_1.EXIT_CODE.SUCCESS;
117
+ }
118
+ }
119
+ const snapshot = await buildSnapshot(process.cwd(), resolved);
120
+ const adf = renderContextAdf(snapshot);
121
+ const markdown = renderContextMarkdown(snapshot);
122
+ fs.mkdirSync(resolved.aiDirAbs, { recursive: true });
123
+ fs.writeFileSync(contextAdfPath, adf, 'utf8');
124
+ fs.writeFileSync(snapshotPath, JSON.stringify(snapshot, null, 2), 'utf8');
125
+ if (resolved.outputPathAbs) {
126
+ fs.mkdirSync(path.dirname(resolved.outputPathAbs), { recursive: true });
127
+ fs.writeFileSync(resolved.outputPathAbs, markdown, 'utf8');
128
+ }
129
+ const status = snapshot.errors.length > 0 || snapshot.warnings.length > 0
130
+ ? 'partial_source_failure'
131
+ : 'refreshed';
132
+ const files = {
133
+ contextAdf: path.relative(process.cwd(), contextAdfPath) || '.',
134
+ snapshotJson: path.relative(process.cwd(), snapshotPath) || '.',
135
+ outputMarkdown: resolved.outputPathAbs
136
+ ? (path.relative(process.cwd(), resolved.outputPathAbs) || '.')
137
+ : null,
138
+ };
139
+ if (options.format === 'json') {
140
+ log(JSON.stringify({
141
+ status: 'ok',
142
+ reason: status,
143
+ generatedAt: snapshot.generatedAt,
144
+ expiresAt: snapshot.expiresAt,
145
+ sourcesRequested: snapshot.sourcesRequested,
146
+ sourcesUsed: snapshot.sourcesUsed,
147
+ files,
148
+ warnings: snapshot.warnings,
149
+ errors: snapshot.errors,
150
+ }, null, 2));
151
+ }
152
+ else {
153
+ log('');
154
+ log(' charter context-refresh');
155
+ log(` Status: ${status}`);
156
+ log(` Sources: ${snapshot.sourcesUsed.join(', ') || '(none)'}`);
157
+ log(` Wrote: ${files.contextAdf}`);
158
+ log(` Snapshot: ${files.snapshotJson}`);
159
+ if (files.outputMarkdown) {
160
+ log(` Mirrored MD: ${files.outputMarkdown}`);
161
+ }
162
+ if (snapshot.warnings.length > 0) {
163
+ log(` Warnings: ${snapshot.warnings.length}`);
164
+ }
165
+ if (snapshot.errors.length > 0) {
166
+ log(` Errors: ${snapshot.errors.length}`);
167
+ }
168
+ log('');
169
+ }
170
+ return index_1.EXIT_CODE.SUCCESS;
171
+ }
172
+ function resolveOptions(options, args) {
173
+ const aiDir = (0, flags_1.getFlag)(args, '--ai-dir') || '.ai';
174
+ const outputPath = (0, flags_1.getFlag)(args, '--output');
175
+ const sourcesFlag = (0, flags_1.getFlag)(args, '--sources');
176
+ const ttlFlag = (0, flags_1.getFlag)(args, '--ttl-minutes');
177
+ const once = args.includes('--once');
178
+ const force = args.includes('--force');
179
+ const config = loadContextConfig(options.configPath);
180
+ const sourcesRequested = parseRequestedSources(sourcesFlag, config.defaults.sources);
181
+ const ttlMinutes = resolveTtlMinutes(ttlFlag, config.defaults.ttlMinutes);
182
+ return {
183
+ aiDirAbs: path.resolve(aiDir),
184
+ outputPathAbs: outputPath ? path.resolve(outputPath) : null,
185
+ ttlMinutes,
186
+ once,
187
+ force,
188
+ sourcesRequested,
189
+ config,
190
+ };
191
+ }
192
+ function loadContextConfig(configPath) {
193
+ const cfgFile = path.resolve(configPath, 'context-sources.json');
194
+ if (!fs.existsSync(cfgFile)) {
195
+ return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
196
+ }
197
+ let parsed;
198
+ try {
199
+ parsed = JSON.parse(fs.readFileSync(cfgFile, 'utf8'));
200
+ }
201
+ catch (error) {
202
+ throw new index_1.CLIError(`Invalid JSON in ${path.relative(process.cwd(), cfgFile)}: ${error.message}`);
203
+ }
204
+ if (!parsed || typeof parsed !== 'object') {
205
+ throw new index_1.CLIError(`Invalid config in ${path.relative(process.cwd(), cfgFile)}: expected an object`);
206
+ }
207
+ const cfg = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
208
+ const root = parsed;
209
+ if (typeof root.version === 'number')
210
+ cfg.version = root.version;
211
+ if (root.defaults && typeof root.defaults === 'object') {
212
+ const defaults = root.defaults;
213
+ if (Array.isArray(defaults.sources)) {
214
+ const cleaned = defaults.sources
215
+ .map((entry) => String(entry).trim().toLowerCase())
216
+ .filter((entry) => entry.length > 0 && SOURCE_SET.has(entry));
217
+ if (cleaned.length > 0)
218
+ cfg.defaults.sources = [...new Set(cleaned)];
219
+ }
220
+ if (typeof defaults.ttlMinutes === 'number' && defaults.ttlMinutes > 0) {
221
+ cfg.defaults.ttlMinutes = Math.floor(defaults.ttlMinutes);
222
+ }
223
+ if (defaults.maxItems && typeof defaults.maxItems === 'object') {
224
+ const maxItems = defaults.maxItems;
225
+ if (typeof maxItems.gitCommits === 'number' && maxItems.gitCommits > 0) {
226
+ cfg.defaults.maxItems.gitCommits = Math.floor(maxItems.gitCommits);
227
+ }
228
+ if (typeof maxItems.gitDirtyFiles === 'number' && maxItems.gitDirtyFiles > 0) {
229
+ cfg.defaults.maxItems.gitDirtyFiles = Math.floor(maxItems.gitDirtyFiles);
230
+ }
231
+ if (typeof maxItems.githubIssues === 'number' && maxItems.githubIssues > 0) {
232
+ cfg.defaults.maxItems.githubIssues = Math.floor(maxItems.githubIssues);
233
+ }
234
+ }
235
+ }
236
+ if (root.sources && typeof root.sources === 'object') {
237
+ const sources = root.sources;
238
+ if (sources.git && typeof sources.git === 'object') {
239
+ const git = sources.git;
240
+ if (typeof git.enabled === 'boolean')
241
+ cfg.sources.git.enabled = git.enabled;
242
+ }
243
+ if (sources.github && typeof sources.github === 'object') {
244
+ const github = sources.github;
245
+ if (typeof github.enabled === 'boolean')
246
+ cfg.sources.github.enabled = github.enabled;
247
+ if (typeof github.repo === 'string')
248
+ cfg.sources.github.repo = github.repo.trim();
249
+ if (Array.isArray(github.labels)) {
250
+ cfg.sources.github.labels = github.labels
251
+ .map((entry) => String(entry).trim())
252
+ .filter((entry) => entry.length > 0);
253
+ }
254
+ if (typeof github.includePullRequests === 'boolean') {
255
+ cfg.sources.github.includePullRequests = github.includePullRequests;
256
+ }
257
+ if (typeof github.includeChecks === 'boolean') {
258
+ cfg.sources.github.includeChecks = github.includeChecks;
259
+ }
260
+ }
261
+ const repoIntelCfg = sources['repo-intel'];
262
+ if (repoIntelCfg && typeof repoIntelCfg === 'object') {
263
+ const ri = repoIntelCfg;
264
+ if (typeof ri.enabled === 'boolean')
265
+ cfg.sources['repo-intel'].enabled = ri.enabled;
266
+ }
267
+ }
268
+ return cfg;
269
+ }
270
+ function parseRequestedSources(sourcesFlag, fallback) {
271
+ if (!sourcesFlag)
272
+ return [...fallback];
273
+ const requested = sourcesFlag
274
+ .split(',')
275
+ .map((entry) => entry.trim().toLowerCase())
276
+ .filter((entry) => entry.length > 0);
277
+ const invalid = requested.filter((entry) => !SOURCE_SET.has(entry));
278
+ if (invalid.length > 0) {
279
+ throw new index_1.CLIError(`Unsupported --sources value(s): ${invalid.join(', ')}. Supported: git, github, repo-intel.`);
280
+ }
281
+ return [...new Set(requested)];
282
+ }
283
+ function resolveTtlMinutes(ttlFlag, defaultTtl) {
284
+ if (!ttlFlag)
285
+ return defaultTtl;
286
+ const parsed = Number(ttlFlag);
287
+ if (!Number.isFinite(parsed) || parsed <= 0) {
288
+ throw new index_1.CLIError(`Invalid --ttl-minutes value: ${ttlFlag}. Must be a positive number.`);
289
+ }
290
+ return Math.floor(parsed);
291
+ }
292
+ function shouldSkipRefresh(snapshotPath, ttlMinutes) {
293
+ if (!fs.existsSync(snapshotPath))
294
+ return { skip: false };
295
+ try {
296
+ const snapshot = JSON.parse(fs.readFileSync(snapshotPath, 'utf8'));
297
+ if (!snapshot.generatedAt)
298
+ return { skip: false };
299
+ const generatedAtMs = Date.parse(snapshot.generatedAt);
300
+ if (!Number.isFinite(generatedAtMs))
301
+ return { skip: false };
302
+ const ageMs = Date.now() - generatedAtMs;
303
+ const ttlMs = ttlMinutes * 60 * 1000;
304
+ if (ageMs <= ttlMs) {
305
+ return { skip: true, snapshot };
306
+ }
307
+ }
308
+ catch {
309
+ return { skip: false };
310
+ }
311
+ return { skip: false };
312
+ }
313
+ async function buildSnapshot(cwd, resolved) {
314
+ const now = new Date();
315
+ const generatedAt = now.toISOString();
316
+ const expiresAt = new Date(now.getTime() + resolved.ttlMinutes * 60_000).toISOString();
317
+ const warnings = [];
318
+ const errors = [];
319
+ const sourcesUsed = [];
320
+ const git = resolved.sourcesRequested.includes('git') && resolved.config.sources.git.enabled
321
+ ? collectGitSnapshot(cwd, resolved.config.defaults.maxItems.gitCommits, resolved.config.defaults.maxItems.gitDirtyFiles)
322
+ : { available: false, branch: null, dirty: false, dirtyFiles: [], recentCommits: [], error: 'disabled' };
323
+ if (git.available) {
324
+ sourcesUsed.push('git');
325
+ }
326
+ else if (resolved.sourcesRequested.includes('git') && git.error && git.error !== 'disabled') {
327
+ warnings.push(`git source unavailable: ${git.error}`);
328
+ }
329
+ const github = resolved.sourcesRequested.includes('github') && resolved.config.sources.github.enabled
330
+ ? await collectGitHubSnapshot(resolved.config, resolved.config.defaults.maxItems.githubIssues)
331
+ : { available: false, repo: null, filterMode: 'strict', labels: [], issues: [], error: 'disabled' };
332
+ if (github.available) {
333
+ sourcesUsed.push('github');
334
+ }
335
+ else if (resolved.sourcesRequested.includes('github') && github.error && github.error !== 'disabled') {
336
+ const msg = `github source unavailable: ${github.error}`;
337
+ warnings.push(msg);
338
+ if (github.error.startsWith('request_failed:') || github.error.startsWith('api_error:') || github.error.startsWith('invalid_json:')) {
339
+ errors.push(msg);
340
+ }
341
+ }
342
+ const repoIntelEnabled = resolved.sourcesRequested.includes('repo-intel') && resolved.config.sources['repo-intel'].enabled;
343
+ const repoIntel = repoIntelEnabled
344
+ ? collectRepoIntelSnapshot(cwd, generatedAt)
345
+ : { available: false, generatedAt, openIssues: [], closedIssues: [], pullRequests: [], releases: [], summary: { openIssueCount: 0, stalledIssues: 0, recurringLabels: [], mergeVelocity: 0, releaseCadence: null }, error: 'disabled' };
346
+ if (repoIntel.available) {
347
+ sourcesUsed.push('repo-intel');
348
+ // Persist full snapshot to .charter/repo-intel/snapshot.json
349
+ const repoIntelSnapshotPath = path.resolve(cwd, '.charter', 'repo-intel', 'snapshot.json');
350
+ fs.mkdirSync(path.dirname(repoIntelSnapshotPath), { recursive: true });
351
+ fs.writeFileSync(repoIntelSnapshotPath, JSON.stringify(repoIntel, null, 2), 'utf8');
352
+ }
353
+ else if (resolved.sourcesRequested.includes('repo-intel') && repoIntel.error && repoIntel.error !== 'disabled') {
354
+ warnings.push(`repo-intel source unavailable: ${repoIntel.error}`);
355
+ }
356
+ const derived = deriveAggregates(git, github, repoIntel);
357
+ return {
358
+ version: 1,
359
+ generatedAt,
360
+ expiresAt,
361
+ repo: {
362
+ root: cwd,
363
+ name: path.basename(cwd) || cwd,
364
+ },
365
+ sourcesRequested: resolved.sourcesRequested,
366
+ sourcesUsed,
367
+ sources: {
368
+ git,
369
+ github,
370
+ 'repo-intel': repoIntel,
371
+ },
372
+ openWork: derived.openWork,
373
+ recentActivity: derived.recentActivity,
374
+ pendingDecisions: derived.pendingDecisions,
375
+ warnings,
376
+ errors,
377
+ };
378
+ }
379
+ function collectGitSnapshot(cwd, commitLimit, dirtyLimit) {
380
+ const inside = runGit(cwd, ['rev-parse', '--is-inside-work-tree']);
381
+ if (inside !== 'true') {
382
+ return {
383
+ available: false,
384
+ branch: null,
385
+ dirty: false,
386
+ dirtyFiles: [],
387
+ recentCommits: [],
388
+ error: 'not a git repository',
389
+ };
390
+ }
391
+ const branch = runGit(cwd, ['rev-parse', '--abbrev-ref', 'HEAD']) ?? 'unknown';
392
+ const status = runGit(cwd, ['status', '--short']) ?? '';
393
+ const dirtyLines = status
394
+ .split(/\r?\n/)
395
+ .map((line) => line.trim())
396
+ .filter((line) => line.length > 0);
397
+ const dirtyFiles = dirtyLines.slice(0, dirtyLimit);
398
+ const log = runGit(cwd, ['log', '-n', String(commitLimit), '--date=iso-strict', '--pretty=format:%h\t%ad\t%s']) ?? '';
399
+ const recentCommits = [];
400
+ for (const line of log.split(/\r?\n/)) {
401
+ if (!line.trim())
402
+ continue;
403
+ const [hash, date, ...subjectParts] = line.split('\t');
404
+ if (!hash || !date || subjectParts.length === 0)
405
+ continue;
406
+ recentCommits.push({
407
+ hash: hash.trim(),
408
+ date: date.trim(),
409
+ subject: subjectParts.join('\t').trim(),
410
+ });
411
+ }
412
+ return {
413
+ available: true,
414
+ branch,
415
+ dirty: dirtyLines.length > 0,
416
+ dirtyFiles,
417
+ recentCommits,
418
+ };
419
+ }
420
+ function runGit(cwd, args) {
421
+ try {
422
+ const output = (0, node_child_process_1.execFileSync)('git', args, {
423
+ cwd,
424
+ encoding: 'utf8',
425
+ stdio: ['ignore', 'pipe', 'pipe'],
426
+ });
427
+ return output.trim();
428
+ }
429
+ catch {
430
+ return null;
431
+ }
432
+ }
433
+ async function collectGitHubSnapshot(config, issueLimit) {
434
+ const repo = config.sources.github.repo;
435
+ if (!repo) {
436
+ return {
437
+ available: false,
438
+ repo: null,
439
+ filterMode: 'strict',
440
+ labels: config.sources.github.labels,
441
+ issues: [],
442
+ error: 'not_configured',
443
+ };
444
+ }
445
+ const token = process.env.GITHUB_TOKEN?.trim();
446
+ if (!token) {
447
+ return {
448
+ available: false,
449
+ repo,
450
+ filterMode: 'strict',
451
+ labels: config.sources.github.labels,
452
+ issues: [],
453
+ error: 'missing GITHUB_TOKEN',
454
+ };
455
+ }
456
+ const labels = config.sources.github.labels.filter((label) => label.length > 0);
457
+ const params = new URLSearchParams({
458
+ state: 'open',
459
+ per_page: String(issueLimit),
460
+ });
461
+ if (labels.length > 0) {
462
+ params.set('labels', labels.join(','));
463
+ }
464
+ const endpoint = `https://api.github.com/repos/${repo}/issues?${params.toString()}`;
465
+ let response;
466
+ try {
467
+ response = await fetch(endpoint, {
468
+ headers: {
469
+ Authorization: `Bearer ${token}`,
470
+ Accept: 'application/vnd.github+json',
471
+ 'User-Agent': 'charter-cli',
472
+ },
473
+ });
474
+ }
475
+ catch (error) {
476
+ return {
477
+ available: false,
478
+ repo,
479
+ filterMode: 'strict',
480
+ labels,
481
+ issues: [],
482
+ error: `request_failed: ${error.message}`,
483
+ };
484
+ }
485
+ if (!response.ok) {
486
+ return {
487
+ available: false,
488
+ repo,
489
+ filterMode: 'strict',
490
+ labels,
491
+ issues: [],
492
+ error: `api_error: ${response.status} ${response.statusText}`,
493
+ };
494
+ }
495
+ let payload = [];
496
+ try {
497
+ payload = await response.json();
498
+ }
499
+ catch (error) {
500
+ return {
501
+ available: false,
502
+ repo,
503
+ filterMode: 'strict',
504
+ labels,
505
+ issues: [],
506
+ error: `invalid_json: ${error.message}`,
507
+ };
508
+ }
509
+ const issues = payload
510
+ .filter((item) => !item.pull_request)
511
+ .map((item) => ({
512
+ number: item.number,
513
+ title: item.title,
514
+ state: item.state,
515
+ updatedAt: item.updated_at,
516
+ labels: item.labels.map((entry) => typeof entry === 'string' ? entry : (entry.name ?? '')).filter((entry) => entry.length > 0),
517
+ url: item.html_url,
518
+ }));
519
+ return {
520
+ available: true,
521
+ repo,
522
+ filterMode: 'strict',
523
+ labels,
524
+ issues,
525
+ };
526
+ }
527
+ function runGhCommand(args, cwd) {
528
+ try {
529
+ const output = (0, node_child_process_1.execFileSync)('gh', args, {
530
+ cwd,
531
+ encoding: 'utf8',
532
+ stdio: ['ignore', 'pipe', 'pipe'],
533
+ });
534
+ return output.trim();
535
+ }
536
+ catch {
537
+ return null;
538
+ }
539
+ }
540
+ function collectRepoIntelSnapshot(cwd, generatedAt) {
541
+ const empty = {
542
+ available: false,
543
+ generatedAt,
544
+ openIssues: [],
545
+ closedIssues: [],
546
+ pullRequests: [],
547
+ releases: [],
548
+ summary: { openIssueCount: 0, stalledIssues: 0, recurringLabels: [], mergeVelocity: 0, releaseCadence: null },
549
+ };
550
+ // Check if gh CLI is available
551
+ const ghVersion = runGhCommand(['--version'], cwd);
552
+ if (!ghVersion) {
553
+ return { ...empty, error: 'gh CLI not available' };
554
+ }
555
+ // Open issues (last 50, sorted by updated)
556
+ const openIssuesRaw = runGhCommand([
557
+ 'issue', 'list', '--limit', '50', '--state', 'open',
558
+ '--json', 'number,title,labels,assignees,createdAt,updatedAt,comments',
559
+ ], cwd);
560
+ if (!openIssuesRaw) {
561
+ return { ...empty, error: 'no GitHub remote or gh auth required' };
562
+ }
563
+ let openIssues;
564
+ try {
565
+ openIssues = JSON.parse(openIssuesRaw);
566
+ }
567
+ catch {
568
+ return { ...empty, error: 'invalid_json: open issues response' };
569
+ }
570
+ // Recent closed issues (last 20)
571
+ const closedIssuesRaw = runGhCommand([
572
+ 'issue', 'list', '--limit', '20', '--state', 'closed',
573
+ '--json', 'number,title,labels,closedAt',
574
+ ], cwd);
575
+ let closedIssues = [];
576
+ if (closedIssuesRaw) {
577
+ try {
578
+ closedIssues = JSON.parse(closedIssuesRaw);
579
+ }
580
+ catch { /* ignore parse failures for supplemental data */ }
581
+ }
582
+ // Recent PRs (last 30, all states)
583
+ const prsRaw = runGhCommand([
584
+ 'pr', 'list', '--limit', '30', '--state', 'all',
585
+ '--json', 'number,title,state,author,mergedAt,createdAt,reviewDecision,labels',
586
+ ], cwd);
587
+ let pullRequests = [];
588
+ if (prsRaw) {
589
+ try {
590
+ pullRequests = JSON.parse(prsRaw);
591
+ }
592
+ catch { /* ignore */ }
593
+ }
594
+ // Release cadence (last 10 releases)
595
+ const releasesRaw = runGhCommand([
596
+ 'release', 'list', '--limit', '10',
597
+ '--json', 'tagName,publishedAt,isLatest',
598
+ ], cwd);
599
+ let releases = [];
600
+ if (releasesRaw) {
601
+ try {
602
+ releases = JSON.parse(releasesRaw);
603
+ }
604
+ catch { /* ignore */ }
605
+ }
606
+ // Compute summary
607
+ const now = Date.now();
608
+ const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
609
+ const stalledIssues = openIssues.filter((issue) => {
610
+ const updatedMs = Date.parse(issue.updatedAt);
611
+ return Number.isFinite(updatedMs) && (now - updatedMs) > thirtyDaysMs;
612
+ }).length;
613
+ // Count label occurrences in closed issues
614
+ const labelCounts = new Map();
615
+ for (const issue of closedIssues) {
616
+ for (const label of issue.labels) {
617
+ const name = label.name;
618
+ labelCounts.set(name, (labelCounts.get(name) ?? 0) + 1);
619
+ }
620
+ }
621
+ const recurringLabels = [...labelCounts.entries()]
622
+ .filter(([, count]) => count >= 3)
623
+ .sort((a, b) => b[1] - a[1])
624
+ .map(([name]) => name);
625
+ const mergeVelocity = pullRequests.filter((pr) => {
626
+ if (!pr.mergedAt)
627
+ return false;
628
+ const mergedMs = Date.parse(pr.mergedAt);
629
+ return Number.isFinite(mergedMs) && (now - mergedMs) <= thirtyDaysMs;
630
+ }).length;
631
+ let releaseCadence = null;
632
+ const lastFiveReleases = releases
633
+ .slice(0, 5)
634
+ .map((r) => Date.parse(r.publishedAt))
635
+ .filter((ms) => Number.isFinite(ms))
636
+ .sort((a, b) => b - a);
637
+ if (lastFiveReleases.length >= 2) {
638
+ const gaps = [];
639
+ for (let i = 0; i < lastFiveReleases.length - 1; i++) {
640
+ gaps.push((lastFiveReleases[i] - lastFiveReleases[i + 1]) / (24 * 60 * 60 * 1000));
641
+ }
642
+ releaseCadence = Math.round(gaps.reduce((a, b) => a + b, 0) / gaps.length);
643
+ }
644
+ const summary = {
645
+ openIssueCount: openIssues.length,
646
+ stalledIssues,
647
+ recurringLabels,
648
+ mergeVelocity,
649
+ releaseCadence,
650
+ };
651
+ return {
652
+ available: true,
653
+ generatedAt,
654
+ openIssues,
655
+ closedIssues,
656
+ pullRequests,
657
+ releases,
658
+ summary,
659
+ };
660
+ }
661
+ function deriveAggregates(git, github, repoIntel) {
662
+ const openWork = [];
663
+ const recentActivity = [];
664
+ const pendingDecisions = [];
665
+ if (git.available) {
666
+ openWork.push({
667
+ source: 'git',
668
+ type: 'branch',
669
+ summary: `Branch ${git.branch ?? 'unknown'}`,
670
+ });
671
+ if (git.dirty) {
672
+ openWork.push({
673
+ source: 'git',
674
+ type: 'working-tree',
675
+ summary: `Working tree has ${git.dirtyFiles.length} pending change(s)`,
676
+ });
677
+ for (const dirty of git.dirtyFiles) {
678
+ openWork.push({
679
+ source: 'git',
680
+ type: 'dirty-file',
681
+ summary: dirty,
682
+ });
683
+ }
684
+ }
685
+ for (const commit of git.recentCommits) {
686
+ recentActivity.push({
687
+ source: 'git',
688
+ type: 'commit',
689
+ summary: `${commit.hash} ${commit.subject}`,
690
+ ref: commit.hash,
691
+ });
692
+ }
693
+ }
694
+ if (github.available) {
695
+ for (const issue of github.issues) {
696
+ openWork.push({
697
+ source: 'github',
698
+ type: 'issue',
699
+ summary: `#${issue.number} ${issue.title}`,
700
+ ref: issue.url,
701
+ });
702
+ pendingDecisions.push({
703
+ source: 'github',
704
+ type: 'issue',
705
+ summary: `Review issue #${issue.number}`,
706
+ ref: issue.url,
707
+ });
708
+ recentActivity.push({
709
+ source: 'github',
710
+ type: 'issue-update',
711
+ summary: `Issue #${issue.number} updated ${issue.updatedAt}`,
712
+ ref: issue.url,
713
+ });
714
+ }
715
+ }
716
+ if (repoIntel.available) {
717
+ const s = repoIntel.summary;
718
+ recentActivity.push({
719
+ source: 'repo-intel',
720
+ type: 'summary',
721
+ summary: `repo-intel: ${s.openIssueCount} open issues, ${s.mergeVelocity} PRs merged in last 30d, ${s.stalledIssues} stalled`,
722
+ });
723
+ if (s.stalledIssues > 0) {
724
+ openWork.push({
725
+ source: 'repo-intel',
726
+ type: 'stalled-issues',
727
+ summary: `${s.stalledIssues} open issue(s) with no activity in 30+ days`,
728
+ });
729
+ }
730
+ if (s.recurringLabels.length > 0) {
731
+ pendingDecisions.push({
732
+ source: 'repo-intel',
733
+ type: 'recurring-labels',
734
+ summary: `Recurring closed-issue labels (≥3 times): ${s.recurringLabels.slice(0, 5).join(', ')}`,
735
+ });
736
+ }
737
+ if (s.releaseCadence !== null) {
738
+ recentActivity.push({
739
+ source: 'repo-intel',
740
+ type: 'release-cadence',
741
+ summary: `Avg release cadence: ~${s.releaseCadence} day(s) between last 5 releases`,
742
+ });
743
+ }
744
+ }
745
+ return { openWork, recentActivity, pendingDecisions };
746
+ }
747
+ function renderContextAdf(snapshot) {
748
+ const lines = [];
749
+ lines.push('ADF: 0.1');
750
+ lines.push('ROLE: Live session snapshot for warm-start context');
751
+ lines.push('');
752
+ lines.push('STATE:');
753
+ lines.push(` GENERATED_AT: ${snapshot.generatedAt}`);
754
+ lines.push(` EXPIRES_AT: ${snapshot.expiresAt}`);
755
+ lines.push(` SOURCES_REQUESTED: ${snapshot.sourcesRequested.join(', ')}`);
756
+ lines.push(` SOURCES_USED: ${snapshot.sourcesUsed.join(', ') || '(none)'}`);
757
+ lines.push(` REPO_ROOT: ${snapshot.repo.name}`);
758
+ lines.push('');
759
+ lines.push('OPEN_WORK:');
760
+ if (snapshot.openWork.length > 0) {
761
+ for (const item of snapshot.openWork) {
762
+ lines.push(` - [${item.source}] ${item.summary}`);
763
+ }
764
+ }
765
+ else {
766
+ lines.push(' - none');
767
+ }
768
+ lines.push('');
769
+ lines.push('RECENT_ACTIVITY:');
770
+ if (snapshot.recentActivity.length > 0) {
771
+ for (const item of snapshot.recentActivity) {
772
+ lines.push(` - [${item.source}] ${item.summary}`);
773
+ }
774
+ }
775
+ else {
776
+ lines.push(' - none');
777
+ }
778
+ lines.push('');
779
+ lines.push('PENDING_DECISIONS:');
780
+ if (snapshot.pendingDecisions.length > 0) {
781
+ for (const item of snapshot.pendingDecisions) {
782
+ lines.push(` - [${item.source}] ${item.summary}`);
783
+ }
784
+ }
785
+ else {
786
+ lines.push(' - none');
787
+ }
788
+ if (snapshot.warnings.length > 0) {
789
+ lines.push('');
790
+ lines.push('WARNINGS:');
791
+ for (const warning of snapshot.warnings) {
792
+ lines.push(` - ${warning}`);
793
+ }
794
+ }
795
+ lines.push('');
796
+ return lines.join('\n');
797
+ }
798
+ function renderContextMarkdown(snapshot) {
799
+ const lines = [];
800
+ lines.push(`# Live Context — ${snapshot.generatedAt}`);
801
+ lines.push('');
802
+ lines.push('## Open Work');
803
+ if (snapshot.openWork.length > 0) {
804
+ for (const item of snapshot.openWork) {
805
+ lines.push(`- [${item.source}] ${item.summary}`);
806
+ }
807
+ }
808
+ else {
809
+ lines.push('- none');
810
+ }
811
+ lines.push('');
812
+ lines.push('## Recent Activity');
813
+ if (snapshot.recentActivity.length > 0) {
814
+ for (const item of snapshot.recentActivity) {
815
+ lines.push(`- [${item.source}] ${item.summary}`);
816
+ }
817
+ }
818
+ else {
819
+ lines.push('- none');
820
+ }
821
+ lines.push('');
822
+ lines.push('## Pending Decisions');
823
+ if (snapshot.pendingDecisions.length > 0) {
824
+ for (const item of snapshot.pendingDecisions) {
825
+ lines.push(`- [${item.source}] ${item.summary}`);
826
+ }
827
+ }
828
+ else {
829
+ lines.push('- none');
830
+ }
831
+ if (snapshot.warnings.length > 0) {
832
+ lines.push('');
833
+ lines.push('## Warnings');
834
+ for (const warning of snapshot.warnings) {
835
+ lines.push(`- ${warning}`);
836
+ }
837
+ }
838
+ lines.push('');
839
+ return lines.join('\n');
840
+ }
841
+ //# sourceMappingURL=context-refresh.js.map