agentloopkit 0.24.1 → 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 +11 -7
- package/dist/cli/index.js +62 -14
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,18 +39,21 @@ Coding agents often move fast but leave reviewers with weak evidence: unclear sc
|
|
|
39
39
|
|
|
40
40
|
## Install
|
|
41
41
|
|
|
42
|
-
Use it with `npx`
|
|
42
|
+
Use it with `npx` from the root of the repository you want to configure:
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
|
|
45
|
+
cd /path/to/your/repo
|
|
46
46
|
npx agentloopkit init --dry-run
|
|
47
|
+
npx agentloopkit init
|
|
47
48
|
```
|
|
48
49
|
|
|
50
|
+
`init` writes files into the current directory. Do not run it from `~` unless you intend to configure your home directory. `--dry-run` previews the file changes without writing them.
|
|
51
|
+
|
|
49
52
|
Pin the current version when you need repeatable CI or team setup:
|
|
50
53
|
|
|
51
54
|
```bash
|
|
52
|
-
npx --yes agentloopkit@0.24.
|
|
53
|
-
npx --yes agentloopkit@0.24.
|
|
55
|
+
npx --yes agentloopkit@0.24.2 version
|
|
56
|
+
npx --yes agentloopkit@0.24.2 init
|
|
54
57
|
```
|
|
55
58
|
|
|
56
59
|
Run the CLI after install:
|
|
@@ -112,6 +115,7 @@ pnpm build
|
|
|
112
115
|
| --------------------------------------- | ------------------------------------------------------------------------------ |
|
|
113
116
|
| `agentloop init` | Generate the repo harness and config |
|
|
114
117
|
| `agentloop init --dry-run` | Preview generated files without writing them |
|
|
118
|
+
| `agentloop init --force` | Allow initialization when the current directory is your home directory |
|
|
115
119
|
| `agentloop doctor` | Check setup health, template version, commands, git state, and risk categories |
|
|
116
120
|
| `agentloop create-task` | Create a task contract in `.agentloop/tasks/` |
|
|
117
121
|
| `agentloop task list` | List task contracts and show the pinned active task |
|
|
@@ -265,7 +269,7 @@ Each contract records:
|
|
|
265
269
|
.agentloop/reports/YYYY-MM-DD-HH-mm-verification-report.md
|
|
266
270
|
```
|
|
267
271
|
|
|
268
|
-
Pass `--task .agentloop/tasks/file.md` to include task title, type, status, and path in the report.
|
|
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.
|
|
269
273
|
|
|
270
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.
|
|
271
275
|
|
|
@@ -380,7 +384,7 @@ See `docs/ci-summary.md`.
|
|
|
380
384
|
```bash
|
|
381
385
|
agentloop release-notes
|
|
382
386
|
agentloop release-notes --from v0.19.0 --to HEAD
|
|
383
|
-
agentloop release-notes --release-version 0.24.
|
|
387
|
+
agentloop release-notes --release-version 0.24.2
|
|
384
388
|
agentloop release-notes --json
|
|
385
389
|
agentloop release-notes --write
|
|
386
390
|
```
|
|
@@ -446,7 +450,7 @@ Use `agentloop check-gates --strict` as a review-evidence gate in pull request C
|
|
|
446
450
|
|
|
447
451
|
CI-generated verification reports include GitHub Actions provenance when available, so reviewers can trace an artifact back to the workflow run that created it.
|
|
448
452
|
|
|
449
|
-
See `docs/github-actions.md`, `examples/github-actions/`, `examples/gitlab-ci/`, and `examples/buildkite/` for copy-pasteable workflows. Pin `agentloopkit@0.24.
|
|
453
|
+
See `docs/github-actions.md`, `examples/github-actions/`, `examples/gitlab-ci/`, and `examples/buildkite/` for copy-pasteable workflows. Pin `agentloopkit@0.24.2` or a newer vetted release when reproducibility matters.
|
|
450
454
|
|
|
451
455
|
## PR Summaries
|
|
452
456
|
|
package/dist/cli/index.js
CHANGED
|
@@ -8,7 +8,8 @@ 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
|
+
import { homedir } from "os";
|
|
12
13
|
|
|
13
14
|
// src/core/constants.ts
|
|
14
15
|
var PACKAGE_NAME = "agentloopkit";
|
|
@@ -196,14 +197,20 @@ async function listFilesRecursive(root, options = {}) {
|
|
|
196
197
|
options.ignore ?? [".git", ".agentloop", "node_modules", "dist", "coverage"]
|
|
197
198
|
);
|
|
198
199
|
const files = [];
|
|
199
|
-
|
|
200
|
+
let inspectedEntries = 0;
|
|
201
|
+
async function walk(current, depth) {
|
|
200
202
|
if (!await pathExists(current)) return;
|
|
203
|
+
if (options.maxEntries !== void 0 && inspectedEntries >= options.maxEntries) return;
|
|
201
204
|
const entries = await readdir(current, { withFileTypes: true }).catch(() => []);
|
|
202
205
|
for (const entry of entries) {
|
|
206
|
+
if (options.maxEntries !== void 0 && inspectedEntries >= options.maxEntries) break;
|
|
207
|
+
inspectedEntries += 1;
|
|
203
208
|
if (ignore.has(entry.name)) continue;
|
|
204
209
|
const absolute = path2.join(current, entry.name);
|
|
205
210
|
if (entry.isDirectory()) {
|
|
206
|
-
|
|
211
|
+
if (options.maxDepth === void 0 || depth < options.maxDepth) {
|
|
212
|
+
await walk(absolute, depth + 1);
|
|
213
|
+
}
|
|
207
214
|
} else if (entry.isFile()) {
|
|
208
215
|
files.push(absolute);
|
|
209
216
|
}
|
|
@@ -211,7 +218,7 @@ async function listFilesRecursive(root, options = {}) {
|
|
|
211
218
|
}
|
|
212
219
|
const rootStat = await stat(root).catch(() => void 0);
|
|
213
220
|
if (!rootStat?.isDirectory()) return [];
|
|
214
|
-
await walk(root);
|
|
221
|
+
await walk(root, 0);
|
|
215
222
|
return files;
|
|
216
223
|
}
|
|
217
224
|
|
|
@@ -247,6 +254,8 @@ var MONOREPO_FILE_MARKERS = [
|
|
|
247
254
|
"lerna.json",
|
|
248
255
|
"rush.json"
|
|
249
256
|
];
|
|
257
|
+
var FALLBACK_PROJECT_DETECTION_MAX_DEPTH = 2;
|
|
258
|
+
var FALLBACK_PROJECT_DETECTION_MAX_ENTRIES = 500;
|
|
250
259
|
async function readPackageJson(cwd) {
|
|
251
260
|
const filePath = path4.join(cwd, "package.json");
|
|
252
261
|
if (!await pathExists(filePath)) return void 0;
|
|
@@ -267,7 +276,10 @@ async function detectProjectType(cwd) {
|
|
|
267
276
|
if (await pathExists(path4.join(cwd, "pyproject.toml")) || await pathExists(path4.join(cwd, "requirements.txt"))) {
|
|
268
277
|
return "python";
|
|
269
278
|
}
|
|
270
|
-
const files = await listFilesRecursive(cwd
|
|
279
|
+
const files = await listFilesRecursive(cwd, {
|
|
280
|
+
maxDepth: FALLBACK_PROJECT_DETECTION_MAX_DEPTH,
|
|
281
|
+
maxEntries: FALLBACK_PROJECT_DETECTION_MAX_ENTRIES
|
|
282
|
+
});
|
|
271
283
|
const relativeFiles = files.map((file) => path4.relative(cwd, file));
|
|
272
284
|
const hasCode = relativeFiles.some(
|
|
273
285
|
(file) => /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|cs)$/.test(file)
|
|
@@ -383,6 +395,13 @@ ${section.trim()}
|
|
|
383
395
|
`);
|
|
384
396
|
result.updated.push(filePath);
|
|
385
397
|
}
|
|
398
|
+
async function resolveComparablePath(filePath) {
|
|
399
|
+
try {
|
|
400
|
+
return await realpath(filePath);
|
|
401
|
+
} catch {
|
|
402
|
+
return path6.resolve(filePath);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
386
405
|
async function initializeAgentLoop(options) {
|
|
387
406
|
const result = {
|
|
388
407
|
created: [],
|
|
@@ -391,6 +410,16 @@ async function initializeAgentLoop(options) {
|
|
|
391
410
|
dryRun: Boolean(options.dryRun)
|
|
392
411
|
};
|
|
393
412
|
const cwd = options.cwd;
|
|
413
|
+
const homeDirectory = options.homeDirectory ?? homedir();
|
|
414
|
+
const [resolvedCwd, resolvedHomeDirectory] = await Promise.all([
|
|
415
|
+
resolveComparablePath(cwd),
|
|
416
|
+
resolveComparablePath(homeDirectory)
|
|
417
|
+
]);
|
|
418
|
+
if (!options.force && resolvedCwd === resolvedHomeDirectory) {
|
|
419
|
+
throw new Error(
|
|
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."
|
|
421
|
+
);
|
|
422
|
+
}
|
|
394
423
|
const packageManager = await detectPackageManager(cwd);
|
|
395
424
|
const projectType = await detectProjectType(cwd);
|
|
396
425
|
const projectName = await detectProjectName(cwd);
|
|
@@ -473,8 +502,12 @@ var consoleLogger = {
|
|
|
473
502
|
|
|
474
503
|
// src/cli/commands/init.ts
|
|
475
504
|
function initCommand() {
|
|
476
|
-
return new Command("init").description("Initialize AgentLoopKit in the current repository").option("--dry-run", "show planned changes without writing files").option("--json", "print machine-readable output").action(async (options) => {
|
|
477
|
-
const result = await initializeAgentLoop({
|
|
505
|
+
return new Command("init").description("Initialize AgentLoopKit in the current repository").option("--dry-run", "show planned changes without writing files").option("--json", "print machine-readable output").option("--force", "allow initialization when the current directory is your home directory").action(async (options) => {
|
|
506
|
+
const result = await initializeAgentLoop({
|
|
507
|
+
cwd: process.cwd(),
|
|
508
|
+
dryRun: options.dryRun,
|
|
509
|
+
force: options.force
|
|
510
|
+
});
|
|
478
511
|
if (options.json) {
|
|
479
512
|
consoleLogger.info(JSON.stringify(result, null, 2));
|
|
480
513
|
return;
|
|
@@ -821,7 +854,17 @@ ${input.rollbackNotes || "Document how to revert or disable this change."}
|
|
|
821
854
|
async function createTaskContractFile(options) {
|
|
822
855
|
const createdDate = options.input.createdDate ?? formatDate();
|
|
823
856
|
const relativePath2 = options.out ?? path9.join(options.config.paths.tasksDir, `${createdDate}-${slugify(options.input.title)}.md`);
|
|
824
|
-
const absolutePath = path9.isAbsolute(relativePath2) ? relativePath2 : path9.
|
|
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
|
+
}
|
|
825
868
|
const markdown = generateTaskContract({ ...options.input, createdDate });
|
|
826
869
|
await writeTextFile(absolutePath, markdown);
|
|
827
870
|
return { path: absolutePath, markdown };
|
|
@@ -1076,10 +1119,16 @@ function isMarkdownTaskPath(taskPath) {
|
|
|
1076
1119
|
const segments = normalized.split("/").filter(Boolean);
|
|
1077
1120
|
return normalized.endsWith(".md") && !segments.some((segment) => segment === ".env" || segment.startsWith(".env."));
|
|
1078
1121
|
}
|
|
1079
|
-
|
|
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) {
|
|
1080
1127
|
if (!taskPath?.trim()) return "";
|
|
1081
1128
|
const cleanPath = taskPath.trim();
|
|
1082
|
-
|
|
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)) {
|
|
1083
1132
|
return `## Task Context
|
|
1084
1133
|
- Path: ${cleanPath}
|
|
1085
1134
|
- Status: unavailable
|
|
@@ -1087,7 +1136,6 @@ async function renderTaskContext(cwd, taskPath) {
|
|
|
1087
1136
|
|
|
1088
1137
|
`;
|
|
1089
1138
|
}
|
|
1090
|
-
const absolutePath = path10.isAbsolute(cleanPath) ? cleanPath : path10.join(cwd, cleanPath);
|
|
1091
1139
|
try {
|
|
1092
1140
|
const markdown = await readFile6(absolutePath, "utf8");
|
|
1093
1141
|
const metadata = parseTaskMetadata(markdown);
|
|
@@ -1146,7 +1194,7 @@ async function runVerification(options) {
|
|
|
1146
1194
|
const branch = await getGitBranch(options.cwd);
|
|
1147
1195
|
const commit = await getGitCommit(options.cwd);
|
|
1148
1196
|
const status = await getGitStatus(options.cwd);
|
|
1149
|
-
const taskContext = await renderTaskContext(options.cwd, options.taskPath);
|
|
1197
|
+
const taskContext = await renderTaskContext(options.cwd, options.config, options.taskPath);
|
|
1150
1198
|
const markdown = `# Verification Report
|
|
1151
1199
|
|
|
1152
1200
|
- Timestamp: ${nowIso}
|
|
@@ -1252,7 +1300,7 @@ function statePath(cwd, config) {
|
|
|
1252
1300
|
function toStoredPath(cwd, absolutePath) {
|
|
1253
1301
|
return path12.relative(cwd, absolutePath).split(path12.sep).join("/");
|
|
1254
1302
|
}
|
|
1255
|
-
function
|
|
1303
|
+
function isInside2(parent, child) {
|
|
1256
1304
|
const relative2 = path12.relative(parent, child);
|
|
1257
1305
|
return relative2 === "" || !relative2.startsWith("..") && !path12.isAbsolute(relative2);
|
|
1258
1306
|
}
|
|
@@ -1281,7 +1329,7 @@ async function resolveTaskPath(options) {
|
|
|
1281
1329
|
const absolutePath = path12.isAbsolute(options.taskPath) ? path12.resolve(options.taskPath) : path12.resolve(options.cwd, options.taskPath);
|
|
1282
1330
|
const root = tasksRoot(options.cwd, options.config);
|
|
1283
1331
|
const displayRoot = options.config.paths.tasksDir;
|
|
1284
|
-
if (!
|
|
1332
|
+
if (!isInside2(root, absolutePath)) {
|
|
1285
1333
|
if (!options.strict) return void 0;
|
|
1286
1334
|
throw new AgentLoopError(`Active task must be inside ${displayRoot}.`);
|
|
1287
1335
|
}
|