@xn-intenton-z2a/agentic-lib 7.1.73 → 7.1.75

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.
@@ -81,10 +81,10 @@ jobs:
81
81
 
82
82
  const SCHEDULE_MAP = {
83
83
  off: null,
84
- weekly: '0 6 * * 1',
85
- daily: '0 6 * * *',
86
- hourly: '0 * * * *',
87
- continuous: '*/15 * * * *',
84
+ weekly: '15 6 * * 1',
85
+ daily: '15 6 * * *',
86
+ hourly: '15 * * * *',
87
+ continuous: '5,15,25,35,45,55 * * * *',
88
88
  };
89
89
 
90
90
  // Update agentic-lib-workflow.yml schedule
@@ -293,44 +293,10 @@ jobs:
293
293
  outputs:
294
294
  telemetry: ${{ steps.gather.outputs.telemetry }}
295
295
 
296
- # ─── Supervisor: LLM decides what to do ─────────────────────────────
297
- supervisor:
298
- needs: [params, pr-cleanup, telemetry]
299
- if: |
300
- always() &&
301
- (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only') &&
302
- needs.params.result == 'success'
303
- runs-on: ubuntu-latest
304
- steps:
305
- - uses: actions/checkout@v6
306
-
307
- - uses: actions/setup-node@v6
308
- with:
309
- node-version: "24"
310
-
311
- - name: Self-init (agentic-lib dev only)
312
- if: hashFiles('scripts/self-init.sh') != '' && hashFiles('.github/agentic-lib/actions/agentic-step/package.json') == ''
313
- run: bash scripts/self-init.sh
314
-
315
- - name: Install agentic-step dependencies
316
- working-directory: .github/agentic-lib/actions/agentic-step
317
- run: npm ci
318
-
319
- - name: Run supervisor
320
- if: github.repository != 'xn-intenton-z2a/agentic-lib'
321
- uses: ./.github/agentic-lib/actions/agentic-step
322
- env:
323
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
324
- COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
325
- with:
326
- task: "supervise"
327
- config: ${{ needs.params.outputs.config-path }}
328
- instructions: ".github/agentic-lib/agents/agent-supervisor.md"
329
- model: ${{ needs.params.outputs.model }}
330
-
331
296
  # ─── Maintain: features + library (push to main) ───────────────────
297
+ # Runs early (parallel with pr-cleanup/telemetry) so supervisor sees features.
332
298
  maintain:
333
- needs: [params, supervisor]
299
+ needs: [params]
334
300
  if: |
335
301
  always() &&
336
302
  (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'maintain-only') &&
@@ -375,6 +341,7 @@ jobs:
375
341
  echo "libraryWritablePaths=${LIBRARY};${SOURCES}" >> $GITHUB_OUTPUT
376
342
 
377
343
  - name: Maintain features
344
+ id: maintain-features
378
345
  if: steps.mission-check.outputs.mission-complete != 'true'
379
346
  uses: ./.github/agentic-lib/actions/agentic-step
380
347
  env:
@@ -388,6 +355,7 @@ jobs:
388
355
  model: ${{ needs.params.outputs.model }}
389
356
 
390
357
  - name: Maintain library
358
+ id: maintain-library
391
359
  if: steps.mission-check.outputs.mission-complete != 'true'
392
360
  uses: ./.github/agentic-lib/actions/agentic-step
393
361
  env:
@@ -400,6 +368,13 @@ jobs:
400
368
  writable-paths: ${{ steps.config.outputs.libraryWritablePaths }}
401
369
  model: ${{ needs.params.outputs.model }}
402
370
 
371
+ - name: Log narrative thread
372
+ if: steps.mission-check.outputs.mission-complete != 'true'
373
+ run: |
374
+ echo "## Maintain Narrative Thread" >> $GITHUB_STEP_SUMMARY
375
+ echo "- **Features:** ${{ steps.maintain-features.outputs.narrative }}" >> $GITHUB_STEP_SUMMARY
376
+ echo "- **Library:** ${{ steps.maintain-library.outputs.narrative }}" >> $GITHUB_STEP_SUMMARY
377
+
403
378
  - name: Commit and push changes
404
379
  if: github.repository != 'xn-intenton-z2a/agentic-lib' && needs.params.outputs.dry-run != 'true'
405
380
  uses: ./.github/agentic-lib/actions/commit-if-changed
@@ -407,6 +382,41 @@ jobs:
407
382
  commit-message: "agentic-step: maintain features and library"
408
383
  push-ref: ${{ github.ref_name }}
409
384
 
385
+ # ─── Supervisor: LLM decides what to do (after maintain has features) ──
386
+ supervisor:
387
+ needs: [params, pr-cleanup, telemetry, maintain]
388
+ if: |
389
+ always() &&
390
+ (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'dev-only') &&
391
+ needs.params.result == 'success'
392
+ runs-on: ubuntu-latest
393
+ steps:
394
+ - uses: actions/checkout@v6
395
+
396
+ - uses: actions/setup-node@v6
397
+ with:
398
+ node-version: "24"
399
+
400
+ - name: Self-init (agentic-lib dev only)
401
+ if: hashFiles('scripts/self-init.sh') != '' && hashFiles('.github/agentic-lib/actions/agentic-step/package.json') == ''
402
+ run: bash scripts/self-init.sh
403
+
404
+ - name: Install agentic-step dependencies
405
+ working-directory: .github/agentic-lib/actions/agentic-step
406
+ run: npm ci
407
+
408
+ - name: Run supervisor
409
+ if: github.repository != 'xn-intenton-z2a/agentic-lib'
410
+ uses: ./.github/agentic-lib/actions/agentic-step
411
+ env:
412
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
413
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
414
+ with:
415
+ task: "supervise"
416
+ config: ${{ needs.params.outputs.config-path }}
417
+ instructions: ".github/agentic-lib/agents/agent-supervisor.md"
418
+ model: ${{ needs.params.outputs.model }}
419
+
410
420
  # ─── Fix stuck PRs with failing checks ─────────────────────────────
411
421
  fix-stuck:
412
422
  needs: [params, supervisor]
@@ -570,7 +580,7 @@ jobs:
570
580
 
571
581
  # ─── Review: close resolved issues, enhance with criteria ──────────
572
582
  review-features:
573
- needs: [params, maintain]
583
+ needs: [params, supervisor]
574
584
  if: |
575
585
  always() &&
576
586
  (needs.params.outputs.mode == 'full' || needs.params.outputs.mode == 'review-only') &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xn-intenton-z2a/agentic-lib",
3
- "version": "7.1.73",
3
+ "version": "7.1.75",
4
4
  "description": "Agentic-lib Agentic Coding Systems SDK powering automated GitHub workflows.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -56,6 +56,8 @@ outputs:
56
56
  description: "Action chosen by the task (e.g. request-supervisor, create-feature, nop)"
57
57
  action-arg:
58
58
  description: "Argument for the chosen action (free text)"
59
+ narrative:
60
+ description: "One-sentence English narrative of what the task did and why"
59
61
 
60
62
  runs:
61
63
  using: "node24"
@@ -83,19 +83,27 @@ export function generateOutline(raw, filePath) {
83
83
  }
84
84
 
85
85
  /**
86
- * Filter issues by recency and label quality.
86
+ * Filter issues by recency, init epoch, and label quality.
87
87
  *
88
88
  * @param {Array} issues - GitHub issue objects
89
89
  * @param {Object} [options]
90
90
  * @param {number} [options.staleDays=30] - Issues older than this with no activity are excluded
91
91
  * @param {boolean} [options.excludeBotOnly=true] - Exclude issues with only bot labels
92
+ * @param {string} [options.initTimestamp] - ISO timestamp; exclude issues created before this epoch
92
93
  * @returns {Array} Filtered issues
93
94
  */
94
95
  export function filterIssues(issues, options = {}) {
95
- const { staleDays = 30, excludeBotOnly = true } = options;
96
+ const { staleDays = 30, excludeBotOnly = true, initTimestamp } = options;
96
97
  const cutoff = Date.now() - staleDays * 86400000;
98
+ const initEpoch = initTimestamp ? new Date(initTimestamp).getTime() : 0;
97
99
 
98
100
  return issues.filter((issue) => {
101
+ // Exclude issues created before the most recent init
102
+ if (initEpoch > 0) {
103
+ const created = new Date(issue.created_at).getTime();
104
+ if (created < initEpoch) return false;
105
+ }
106
+
99
107
  const lastActivity = new Date(issue.updated_at || issue.created_at).getTime();
100
108
  if (lastActivity < cutoff) return false;
101
109
 
@@ -398,6 +406,28 @@ async function _runCopilotTaskOnce({
398
406
  }
399
407
  }
400
408
 
409
+ /**
410
+ * Extract a [NARRATIVE] line from an LLM response.
411
+ * Returns the text after the tag, or a fallback summary.
412
+ *
413
+ * @param {string} content - Raw LLM response content
414
+ * @param {string} [fallback] - Fallback if no [NARRATIVE] tag found
415
+ * @returns {string} The narrative sentence
416
+ */
417
+ export function extractNarrative(content, fallback) {
418
+ if (!content) return fallback || "";
419
+ const match = content.match(/\[NARRATIVE\]\s*(.+)/);
420
+ if (match) return match[1].trim();
421
+ return fallback || "";
422
+ }
423
+
424
+ /**
425
+ * Narrative solicitation to append to system messages.
426
+ * Asks the LLM to end its response with a one-sentence summary.
427
+ */
428
+ export const NARRATIVE_INSTRUCTION =
429
+ "\n\nAfter completing your task, end your response with a line starting with [NARRATIVE] followed by one plain English sentence describing what you did and why, for the activity log.";
430
+
401
431
  /**
402
432
  * Read a file, returning empty string on failure. For optional context files.
403
433
  *
@@ -98,6 +98,7 @@ async function run() {
98
98
  if (result.model) core.setOutput("model", result.model);
99
99
  if (result.action) core.setOutput("action", result.action);
100
100
  if (result.actionArg) core.setOutput("action-arg", result.actionArg);
101
+ if (result.narrative) core.setOutput("narrative", result.narrative);
101
102
 
102
103
  const profileName = config.tuning?.profileName || "unknown";
103
104
 
@@ -187,8 +187,10 @@ export async function discussions(context) {
187
187
  const { data: openIssues } = await octokit.rest.issues.listForRepo({
188
188
  ...repo, state: "open", per_page: 10, sort: "created", direction: "asc",
189
189
  });
190
+ const initTimestampForIssues = config?.init?.timestamp || null;
191
+ const initEpochForIssues = initTimestampForIssues ? new Date(initTimestampForIssues).getTime() : 0;
190
192
  repoContext.issuesSummary = openIssues
191
- .filter((i) => !i.pull_request)
193
+ .filter((i) => !i.pull_request && (initEpochForIssues <= 0 || new Date(i.created_at).getTime() >= initEpochForIssues))
192
194
  .map((i) => {
193
195
  const labels = i.labels.map((l) => l.name).join(", ");
194
196
  return `#${i.number}: ${i.title} [${labels || "no labels"}]`;
@@ -238,6 +240,14 @@ export async function discussions(context) {
238
240
  }
239
241
 
240
242
  const discussion = await fetchDiscussion(octokit, discussionUrl, t.discussionComments || 10);
243
+
244
+ // Filter discussion comments to only those after the most recent init
245
+ const initTs = config?.init?.timestamp || null;
246
+ if (initTs && discussion.comments.length > 0) {
247
+ const initDate = new Date(initTs);
248
+ discussion.comments = discussion.comments.filter((c) => new Date(c.createdAt) >= initDate);
249
+ }
250
+
241
251
  const prompt = buildPrompt(discussionUrl, discussion, context, t, repoContext);
242
252
  const { content, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
243
253
  model,
@@ -336,6 +346,7 @@ export async function discussions(context) {
336
346
  cost,
337
347
  model,
338
348
  details: `Action: ${action}${argSuffix}\nReply: ${replyBody.substring(0, 200)}`,
349
+ narrative: `Responded to discussion with action ${action}${argSuffix}.`,
339
350
  action,
340
351
  actionArg,
341
352
  replyBody,
@@ -102,6 +102,7 @@ async function enhanceSingleIssue({ octokit, repo, config, issueNumber, instruct
102
102
  cost,
103
103
  model,
104
104
  details: `Enhanced issue #${issueNumber} with acceptance criteria`,
105
+ narrative: `Enhanced issue #${issueNumber} with testable acceptance criteria.`,
105
106
  };
106
107
  }
107
108
 
@@ -173,5 +174,6 @@ export async function enhanceIssue(context) {
173
174
  .map((r) => r.details)
174
175
  .join("; ")
175
176
  .substring(0, 500)}`,
177
+ narrative: `Enhanced ${enhanced} issue(s) with testable acceptance criteria.`,
176
178
  };
177
179
  }
@@ -9,7 +9,7 @@
9
9
  import * as core from "@actions/core";
10
10
  import { readFileSync } from "fs";
11
11
  import { execSync } from "child_process";
12
- import { runCopilotTask, formatPathsSection } from "../copilot.js";
12
+ import { runCopilotTask, formatPathsSection, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
13
13
 
14
14
  /**
15
15
  * Extract run_id from a check run's details_url.
@@ -100,7 +100,7 @@ async function resolveConflicts({ config, pr, prNumber, instructions, model, wri
100
100
  const t = config.tuning || {};
101
101
  const { tokensUsed, inputTokens, outputTokens, cost, content: resultContent } = await runCopilotTask({
102
102
  model,
103
- systemMessage: `You are resolving git merge conflicts on PR #${prNumber}. Write resolved versions of each conflicted file, removing all conflict markers. Preserve the PR's feature intent while incorporating main's updates.`,
103
+ systemMessage: `You are resolving git merge conflicts on PR #${prNumber}. Write resolved versions of each conflicted file, removing all conflict markers. Preserve the PR's feature intent while incorporating main's updates.` + NARRATIVE_INSTRUCTION,
104
104
  prompt,
105
105
  writablePaths,
106
106
  tuning: t,
@@ -116,7 +116,7 @@ async function resolveConflicts({ config, pr, prNumber, instructions, model, wri
116
116
  cost,
117
117
  model,
118
118
  details: `Resolved ${conflicts.length} conflicted file(s) on PR #${prNumber}`,
119
- narrative: (resultContent || "").substring(0, 2000),
119
+ narrative: extractNarrative(resultContent, `Resolved ${conflicts.length} merge conflict(s) on PR #${prNumber}.`),
120
120
  };
121
121
  }
122
122
 
@@ -189,7 +189,7 @@ export async function fixCode(context) {
189
189
  const t = config.tuning || {};
190
190
  const { tokensUsed, inputTokens, outputTokens, cost, content: resultContent } = await runCopilotTask({
191
191
  model,
192
- systemMessage: `You are an autonomous coding agent fixing failing tests on PR #${prNumber}. Make minimal, targeted changes to fix the test failures.`,
192
+ systemMessage: `You are an autonomous coding agent fixing failing tests on PR #${prNumber}. Make minimal, targeted changes to fix the test failures.` + NARRATIVE_INSTRUCTION,
193
193
  prompt,
194
194
  writablePaths,
195
195
  tuning: t,
@@ -205,6 +205,6 @@ export async function fixCode(context) {
205
205
  cost,
206
206
  model,
207
207
  details: `Applied fix for ${failedChecks.length} failing check(s) on PR #${prNumber}`,
208
- narrative: (resultContent || "").substring(0, 2000),
208
+ narrative: extractNarrative(resultContent, `Fixed ${failedChecks.length} failing check(s) on PR #${prNumber}.`),
209
209
  };
210
210
  }
@@ -6,7 +6,7 @@
6
6
  // prunes completed/irrelevant features, and ensures quality.
7
7
 
8
8
  import { existsSync } from "fs";
9
- import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection, extractFeatureSummary } from "../copilot.js";
9
+ import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection, extractFeatureSummary, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
10
10
  import { checkWipLimit } from "../safety.js";
11
11
 
12
12
  /**
@@ -45,13 +45,20 @@ export async function maintainFeatures(context) {
45
45
  contentLimit: t.documentSummary || 1000,
46
46
  });
47
47
 
48
- const { data: closedIssues } = await octokit.rest.issues.listForRepo({
48
+ // Filter closed issues to only those created after the most recent init
49
+ const initTimestamp = config.init?.timestamp || null;
50
+ const initEpoch = initTimestamp ? new Date(initTimestamp).getTime() : 0;
51
+
52
+ const { data: closedIssuesRaw } = await octokit.rest.issues.listForRepo({
49
53
  ...repo,
50
54
  state: "closed",
51
55
  per_page: t.issuesScan || 20,
52
56
  sort: "updated",
53
57
  direction: "desc",
54
58
  });
59
+ const closedIssues = initEpoch > 0
60
+ ? closedIssuesRaw.filter((i) => new Date(i.created_at).getTime() >= initEpoch)
61
+ : closedIssuesRaw;
55
62
 
56
63
  const agentInstructions = instructions || "Maintain the feature set by creating, updating, or pruning features.";
57
64
 
@@ -68,7 +75,7 @@ export async function maintainFeatures(context) {
68
75
  libraryDocs.length > 0 ? `## Library Documents (${libraryDocs.length})` : "",
69
76
  ...libraryDocs.map((d) => `### ${d.name}\n${d.content}`),
70
77
  "",
71
- `## Recently Closed Issues (${closedIssues.length})`,
78
+ `## Recently Closed Issues (${closedIssues.length}${initTimestamp ? `, since init ${initTimestamp}` : ""})`,
72
79
  ...closedIssues.slice(0, Math.floor((t.issuesScan || 20) / 2)).map((i) => `- #${i.number}: ${i.title}`),
73
80
  "",
74
81
  "## Your Task",
@@ -83,10 +90,10 @@ export async function maintainFeatures(context) {
83
90
  "- Feature files must be markdown with a descriptive filename (e.g. HTTP_SERVER.md)",
84
91
  ].join("\n");
85
92
 
86
- const { tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
93
+ const { content: resultContent, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
87
94
  model,
88
95
  systemMessage:
89
- "You are a feature lifecycle manager. Create, update, and prune feature specification files to keep the project focused on its mission.",
96
+ "You are a feature lifecycle manager. Create, update, and prune feature specification files to keep the project focused on its mission." + NARRATIVE_INSTRUCTION,
90
97
  prompt,
91
98
  writablePaths,
92
99
  tuning: t,
@@ -100,5 +107,6 @@ export async function maintainFeatures(context) {
100
107
  cost,
101
108
  model,
102
109
  details: `Maintained features (${features.length} existing, limit ${featureLimit})`,
110
+ narrative: extractNarrative(resultContent, `Maintained ${features.length} features (limit ${featureLimit}).`),
103
111
  };
104
112
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  import * as core from "@actions/core";
9
9
  import { existsSync } from "fs";
10
- import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection } from "../copilot.js";
10
+ import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
11
11
 
12
12
  /**
13
13
  * Maintain the library of knowledge documents from source URLs.
@@ -81,10 +81,10 @@ export async function maintainLibrary(context) {
81
81
  ].join("\n");
82
82
  }
83
83
 
84
- const { tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
84
+ const { content: resultContent, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
85
85
  model,
86
86
  systemMessage:
87
- "You are a knowledge librarian. Maintain a library of technical documents extracted from web sources.",
87
+ "You are a knowledge librarian. Maintain a library of technical documents extracted from web sources." + NARRATIVE_INSTRUCTION,
88
88
  prompt,
89
89
  writablePaths,
90
90
  tuning: t,
@@ -103,5 +103,6 @@ export async function maintainLibrary(context) {
103
103
  cost,
104
104
  model,
105
105
  details: detailsMsg,
106
+ narrative: extractNarrative(resultContent, detailsMsg),
106
107
  };
107
108
  }
@@ -7,7 +7,7 @@
7
7
 
8
8
  import * as core from "@actions/core";
9
9
  import { checkAttemptLimit, checkWipLimit, isIssueResolved } from "../safety.js";
10
- import { runCopilotTask, readOptionalFile, formatPathsSection } from "../copilot.js";
10
+ import { runCopilotTask, readOptionalFile, formatPathsSection, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
11
11
 
12
12
  /**
13
13
  * Resolve a GitHub issue by generating code and creating a PR.
@@ -81,7 +81,7 @@ export async function resolveIssue(context) {
81
81
  const t = config.tuning || {};
82
82
  const { content: resultContent, tokensUsed, inputTokens, outputTokens, cost } = await runCopilotTask({
83
83
  model,
84
- systemMessage: `You are an autonomous coding agent resolving GitHub issue #${issueNumber}. Write clean, tested code. Only modify files listed under "Writable" paths. Read-only paths are for context only.`,
84
+ systemMessage: `You are an autonomous coding agent resolving GitHub issue #${issueNumber}. Write clean, tested code. Only modify files listed under "Writable" paths. Read-only paths are for context only.` + NARRATIVE_INSTRUCTION,
85
85
  prompt,
86
86
  writablePaths,
87
87
  tuning: t,
@@ -99,6 +99,6 @@ export async function resolveIssue(context) {
99
99
  model,
100
100
  commitUrl: null,
101
101
  details: `Generated code for issue #${issueNumber}: ${resultContent.substring(0, 200)}`,
102
- narrative: (resultContent || "").substring(0, 2000),
102
+ narrative: extractNarrative(resultContent, `Generated code for issue #${issueNumber}.`),
103
103
  };
104
104
  }
@@ -161,6 +161,7 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
161
161
  cost,
162
162
  model,
163
163
  details: `Closed issue #${targetIssueNumber}: ${verdict.substring(0, 200)}`,
164
+ narrative: `Reviewed issue #${targetIssueNumber} and closed it as resolved.`,
164
165
  };
165
166
  }
166
167
 
@@ -173,6 +174,7 @@ async function reviewSingleIssue({ octokit, repo, config, targetIssueNumber, ins
173
174
  cost,
174
175
  model,
175
176
  details: `Issue #${targetIssueNumber} remains open: ${verdict.substring(0, 200)}`,
177
+ narrative: `Reviewed issue #${targetIssueNumber} — still open, not yet resolved.`,
176
178
  };
177
179
  }
178
180
 
@@ -271,5 +273,6 @@ export async function reviewIssue(context) {
271
273
  .map((r) => r.details)
272
274
  .join("; ")
273
275
  .substring(0, 500)}`,
276
+ narrative: `Reviewed ${reviewed} issue(s), closed ${closed} as resolved.`,
274
277
  };
275
278
  }
@@ -51,6 +51,9 @@ async function gatherContext(octokit, repo, config, t) {
51
51
  : [];
52
52
  const libraryLimit = config.paths.library?.limit || 32;
53
53
 
54
+ // Read init timestamp for epoch boundary (used by issue filtering below)
55
+ const initTimestamp = config.init?.timestamp || null;
56
+
54
57
  const { data: openIssues } = await octokit.rest.issues.listForRepo({
55
58
  ...repo,
56
59
  state: "open",
@@ -59,7 +62,7 @@ async function gatherContext(octokit, repo, config, t) {
59
62
  direction: "asc",
60
63
  });
61
64
  const issuesOnly = openIssues.filter((i) => !i.pull_request);
62
- const filteredIssues = filterIssues(issuesOnly, { staleDays: t.staleDays || 30 });
65
+ const filteredIssues = filterIssues(issuesOnly, { staleDays: t.staleDays || 30, initTimestamp });
63
66
  const oldestReadyIssue = filteredIssues.find((i) => i.labels.some((l) => l.name === "ready"));
64
67
  const issuesSummary = filteredIssues.map((i) => {
65
68
  const age = Math.floor((Date.now() - new Date(i.created_at).getTime()) / 86400000);
@@ -77,7 +80,11 @@ async function gatherContext(octokit, repo, config, t) {
77
80
  sort: "updated",
78
81
  direction: "desc",
79
82
  });
80
- for (const ci of closedIssuesRaw.filter((i) => !i.pull_request)) {
83
+ const initEpoch = initTimestamp ? new Date(initTimestamp).getTime() : 0;
84
+ const closedIssuesFiltered = closedIssuesRaw.filter((i) =>
85
+ !i.pull_request && (initEpoch <= 0 || new Date(i.created_at).getTime() >= initEpoch)
86
+ );
87
+ for (const ci of closedIssuesFiltered) {
81
88
  let closeReason = "closed";
82
89
  try {
83
90
  const { data: comments } = await octokit.rest.issues.listComments({
@@ -110,9 +117,6 @@ async function gatherContext(octokit, repo, config, t) {
110
117
  return `#${pr.number}: ${pr.title} (${pr.head.ref}) [${labels || "no labels"}] (${age}d old)`;
111
118
  });
112
119
 
113
- // Read init timestamp for epoch boundary
114
- const initTimestamp = config.init?.timestamp || null;
115
-
116
120
  let workflowsSummary = [];
117
121
  let actionsSinceInit = [];
118
122
  try {
@@ -7,7 +7,7 @@
7
7
 
8
8
  import * as core from "@actions/core";
9
9
  import { writeFileSync, existsSync } from "fs";
10
- import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection, filterIssues, summariseIssue, extractFeatureSummary } from "../copilot.js";
10
+ import { runCopilotTask, readOptionalFile, scanDirectory, formatPathsSection, filterIssues, summariseIssue, extractFeatureSummary, extractNarrative, NARRATIVE_INSTRUCTION } from "../copilot.js";
11
11
 
12
12
  /**
13
13
  * Run the full transformation pipeline from mission to code.
@@ -162,7 +162,7 @@ export async function transform(context) {
162
162
  } = await runCopilotTask({
163
163
  model,
164
164
  systemMessage:
165
- "You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step.",
165
+ "You are an autonomous code transformation agent. Your goal is to advance the repository toward its mission by making the most impactful change possible in a single step." + NARRATIVE_INSTRUCTION,
166
166
  prompt,
167
167
  writablePaths,
168
168
  tuning: t,
@@ -201,7 +201,7 @@ export async function transform(context) {
201
201
  cost,
202
202
  model,
203
203
  details: resultContent.substring(0, 500),
204
- narrative: (resultContent || "").substring(0, 2000),
204
+ narrative: extractNarrative(resultContent, "Transformation step completed."),
205
205
  promptBudget,
206
206
  contextNotes: `Transformed with ${sourceFiles.length} source files (mtime-sorted, cleaned), ${features.length} features, ${openIssues.length} issues (${rawIssues.length - openIssues.length} stale filtered).`,
207
207
  };
@@ -324,7 +324,7 @@ async function transformTdd({
324
324
  const phase2 = await runCopilotTask({
325
325
  model,
326
326
  systemMessage:
327
- "You are a TDD agent. A failing test was written in Phase 1. Write the minimum implementation to make it pass. Do not modify the test.",
327
+ "You are a TDD agent. A failing test was written in Phase 1. Write the minimum implementation to make it pass. Do not modify the test." + NARRATIVE_INSTRUCTION,
328
328
  prompt: implPrompt,
329
329
  writablePaths,
330
330
  tuning: t,
@@ -341,5 +341,6 @@ async function transformTdd({
341
341
  cost: (phase1.cost || 0) + (phase2.cost || 0),
342
342
  model,
343
343
  details: `TDD transformation: Phase 1 (failing test) + Phase 2 (implementation). ${testResult.substring(0, 200)}`,
344
+ narrative: extractNarrative(phase2.content, "TDD transformation: wrote failing test then implementation."),
344
345
  };
345
346
  }
@@ -16,7 +16,7 @@
16
16
  "author": "",
17
17
  "license": "MIT",
18
18
  "dependencies": {
19
- "@xn-intenton-z2a/agentic-lib": "^7.1.73"
19
+ "@xn-intenton-z2a/agentic-lib": "^7.1.75"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@vitest/coverage-v8": "^4.0.18",