agentloopkit 0.24.2 → 0.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -269,7 +269,7 @@ Each contract records:
269
269
  .agentloop/reports/YYYY-MM-DD-HH-mm-verification-report.md
270
270
  ```
271
271
 
272
- Pass `--task .agentloop/tasks/file.md` to include task title, type, status, and path in the report. AgentLoopKit reads Markdown task contracts for this flag. It reports `.env`-style paths as unavailable instead of reading them.
272
+ Pass `--task .agentloop/tasks/file.md` to include task title, type, status, and path in the report. The path must point to a Markdown task contract inside the configured task directory. Invalid paths are reported as unavailable instead of being read.
273
273
 
274
274
  It does not hide failures. Failed reports include a short failure summary with each failed command, exit code, and final useful output lines before the full command output. If long logs are truncated, the report keeps the first and last output so the final error stays visible. If no commands are configured, it writes a report saying nothing was verified.
275
275
 
package/dist/cli/index.js CHANGED
@@ -8,7 +8,7 @@ import { Command } from "commander";
8
8
 
9
9
  // src/core/init.ts
10
10
  import path6 from "path";
11
- import { readdir as readdir3 } from "fs/promises";
11
+ import { readdir as readdir3, realpath } from "fs/promises";
12
12
  import { homedir } from "os";
13
13
 
14
14
  // src/core/constants.ts
@@ -395,6 +395,13 @@ ${section.trim()}
395
395
  `);
396
396
  result.updated.push(filePath);
397
397
  }
398
+ async function resolveComparablePath(filePath) {
399
+ try {
400
+ return await realpath(filePath);
401
+ } catch {
402
+ return path6.resolve(filePath);
403
+ }
404
+ }
398
405
  async function initializeAgentLoop(options) {
399
406
  const result = {
400
407
  created: [],
@@ -404,7 +411,11 @@ async function initializeAgentLoop(options) {
404
411
  };
405
412
  const cwd = options.cwd;
406
413
  const homeDirectory = options.homeDirectory ?? homedir();
407
- if (!options.dryRun && !options.force && path6.resolve(cwd) === path6.resolve(homeDirectory)) {
414
+ const [resolvedCwd, resolvedHomeDirectory] = await Promise.all([
415
+ resolveComparablePath(cwd),
416
+ resolveComparablePath(homeDirectory)
417
+ ]);
418
+ if (!options.force && resolvedCwd === resolvedHomeDirectory) {
408
419
  throw new Error(
409
420
  "Refusing to initialize your home directory. Run this inside a project repository, or pass --force if you intentionally want AgentLoopKit files in your home directory."
410
421
  );
@@ -843,7 +854,17 @@ ${input.rollbackNotes || "Document how to revert or disable this change."}
843
854
  async function createTaskContractFile(options) {
844
855
  const createdDate = options.input.createdDate ?? formatDate();
845
856
  const relativePath2 = options.out ?? path9.join(options.config.paths.tasksDir, `${createdDate}-${slugify(options.input.title)}.md`);
846
- const absolutePath = path9.isAbsolute(relativePath2) ? relativePath2 : path9.join(options.cwd, relativePath2);
857
+ const absolutePath = path9.isAbsolute(relativePath2) ? path9.resolve(relativePath2) : path9.resolve(options.cwd, relativePath2);
858
+ const tasksRoot2 = path9.resolve(options.cwd, options.config.paths.tasksDir);
859
+ const relativeToTasks = path9.relative(tasksRoot2, absolutePath);
860
+ if (relativeToTasks === "" || relativeToTasks.startsWith("..") || path9.isAbsolute(relativeToTasks)) {
861
+ throw new AgentLoopError(
862
+ `Task output path must stay inside ${options.config.paths.tasksDir}.`
863
+ );
864
+ }
865
+ if (!absolutePath.endsWith(".md")) {
866
+ throw new AgentLoopError("Task output path must be a Markdown file.");
867
+ }
847
868
  const markdown = generateTaskContract({ ...options.input, createdDate });
848
869
  await writeTextFile(absolutePath, markdown);
849
870
  return { path: absolutePath, markdown };
@@ -1098,10 +1119,16 @@ function isMarkdownTaskPath(taskPath) {
1098
1119
  const segments = normalized.split("/").filter(Boolean);
1099
1120
  return normalized.endsWith(".md") && !segments.some((segment) => segment === ".env" || segment.startsWith(".env."));
1100
1121
  }
1101
- async function renderTaskContext(cwd, taskPath) {
1122
+ function isInside(parent, child) {
1123
+ const relative2 = path10.relative(parent, child);
1124
+ return relative2 === "" || !relative2.startsWith("..") && !path10.isAbsolute(relative2);
1125
+ }
1126
+ async function renderTaskContext(cwd, config, taskPath) {
1102
1127
  if (!taskPath?.trim()) return "";
1103
1128
  const cleanPath = taskPath.trim();
1104
- if (!isMarkdownTaskPath(cleanPath)) {
1129
+ const absolutePath = path10.isAbsolute(cleanPath) ? path10.resolve(cleanPath) : path10.resolve(cwd, cleanPath);
1130
+ const tasksRoot2 = path10.resolve(cwd, config.paths.tasksDir);
1131
+ if (!isMarkdownTaskPath(cleanPath) || !isInside(tasksRoot2, absolutePath)) {
1105
1132
  return `## Task Context
1106
1133
  - Path: ${cleanPath}
1107
1134
  - Status: unavailable
@@ -1109,7 +1136,6 @@ async function renderTaskContext(cwd, taskPath) {
1109
1136
 
1110
1137
  `;
1111
1138
  }
1112
- const absolutePath = path10.isAbsolute(cleanPath) ? cleanPath : path10.join(cwd, cleanPath);
1113
1139
  try {
1114
1140
  const markdown = await readFile6(absolutePath, "utf8");
1115
1141
  const metadata = parseTaskMetadata(markdown);
@@ -1168,7 +1194,7 @@ async function runVerification(options) {
1168
1194
  const branch = await getGitBranch(options.cwd);
1169
1195
  const commit = await getGitCommit(options.cwd);
1170
1196
  const status = await getGitStatus(options.cwd);
1171
- const taskContext = await renderTaskContext(options.cwd, options.taskPath);
1197
+ const taskContext = await renderTaskContext(options.cwd, options.config, options.taskPath);
1172
1198
  const markdown = `# Verification Report
1173
1199
 
1174
1200
  - Timestamp: ${nowIso}
@@ -1274,7 +1300,7 @@ function statePath(cwd, config) {
1274
1300
  function toStoredPath(cwd, absolutePath) {
1275
1301
  return path12.relative(cwd, absolutePath).split(path12.sep).join("/");
1276
1302
  }
1277
- function isInside(parent, child) {
1303
+ function isInside2(parent, child) {
1278
1304
  const relative2 = path12.relative(parent, child);
1279
1305
  return relative2 === "" || !relative2.startsWith("..") && !path12.isAbsolute(relative2);
1280
1306
  }
@@ -1303,7 +1329,7 @@ async function resolveTaskPath(options) {
1303
1329
  const absolutePath = path12.isAbsolute(options.taskPath) ? path12.resolve(options.taskPath) : path12.resolve(options.cwd, options.taskPath);
1304
1330
  const root = tasksRoot(options.cwd, options.config);
1305
1331
  const displayRoot = options.config.paths.tasksDir;
1306
- if (!isInside(root, absolutePath)) {
1332
+ if (!isInside2(root, absolutePath)) {
1307
1333
  if (!options.strict) return void 0;
1308
1334
  throw new AgentLoopError(`Active task must be inside ${displayRoot}.`);
1309
1335
  }