@rtorcato/repo-tooling 3.39.0 → 3.40.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/AGENTS.md +2 -0
- package/dist/cli/commands/loop-env.js +76 -0
- package/dist/cli/index.js +9 -0
- package/package.json +1 -1
- package/skills/ai-issue-loop/SKILL.md +275 -684
package/AGENTS.md
CHANGED
|
@@ -27,6 +27,7 @@ Every command supports `--json` and a non-interactive mode. Combine with `--yes`
|
|
|
27
27
|
| `list --json` | ✅ | ✅ | Enumerate the library's surface area. Each entry has `{ name, description, exports, fixTarget }`. |
|
|
28
28
|
| `copy <name>` | ✅ | text only | Copy a single preset (`biome`, `tsconfig`) into the current directory. |
|
|
29
29
|
| `loop guard --root <path>` | ✅ | ✅ | Guard an `ai-issue-loop` tick: repair a wrongly-bare main checkout, gate the `node_modules` rebuild (`--removed`), and assert `gh` authenticates as the declared `rules.aiLoop.agentUser`. Exit `0` continue, `1` repair failed, `2` root is not a repairable checkout or the agent identity is wrong — both non-zero halt the tick. |
|
|
30
|
+
| `loop env` | ✅ | ✅ | Resolve an `ai-issue-loop` tick's variables once: `root` (main checkout, correct from inside a worktree), `worktreeRoot`, `ownerRepo`, `agentUser` (empty unless an assignable collaborator), `humanUser` (empty for org repos), `me`. Without `--json`, prints `KEY='value'` lines for `eval`. Exit `1` when the checkout or its GitHub repo cannot be resolved. |
|
|
30
31
|
|
|
31
32
|
## Recommended workflows
|
|
32
33
|
|
|
@@ -117,6 +118,7 @@ A fixer may also **refuse** — the target file holds something the generator ca
|
|
|
117
118
|
- `src/cli/commands/fix.ts` — `Fixer` interface, fixer registry, `fixCommand`
|
|
118
119
|
- `src/cli/commands/fix-targets.ts` — shared check → fix target map (used by both doctor's footer and fix's lookup)
|
|
119
120
|
- `src/cli/commands/loop-guard.ts` — `loop guard`: the `--is-inside-work-tree` / `.git` invariant table and the `node_modules` rebuild gate, drained out of the ai-issue-loop skill's prose (#519)
|
|
121
|
+
- `src/cli/commands/loop-env.ts` — `loop env`: the skill's Pass 0 variables resolved in one call (#615)
|
|
120
122
|
- `src/cli/generators/` — one file per concern (linting, testing, build, git, github-actions, security, misc)
|
|
121
123
|
- `tooling/` — every shipped preset, mirrored 1:1 with `package.json` `exports`
|
|
122
124
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { LOGIN } from '../../base/agent-user.js';
|
|
3
|
+
import { realGitExec } from '../../base/git-identity.js';
|
|
4
|
+
import { realGhExec } from '../../base/github-settings.js';
|
|
5
|
+
import { configuredAgentUser, defaultWorktreeRoot } from './loop-guard.js';
|
|
6
|
+
async function ghOut(gh, args) {
|
|
7
|
+
const r = await gh(args);
|
|
8
|
+
return r.ok ? r.stdout.trim() : '';
|
|
9
|
+
}
|
|
10
|
+
export async function resolveLoopEnv(options = {}) {
|
|
11
|
+
const dir = path.resolve(options.dir ?? process.cwd());
|
|
12
|
+
const git = options.git ?? ((args) => realGitExec(args, dir));
|
|
13
|
+
const gh = options.gh ?? ((args, stdin) => realGhExec(args, stdin, dir));
|
|
14
|
+
const env = options.env ?? process.env;
|
|
15
|
+
const warnings = [];
|
|
16
|
+
// Absolute, so the common dir is `<main checkout>/.git` from anywhere.
|
|
17
|
+
const common = (await git(['rev-parse', '--path-format=absolute', '--git-common-dir']))?.trim();
|
|
18
|
+
const root = common ? path.resolve(common, '..') : '';
|
|
19
|
+
if (!root)
|
|
20
|
+
warnings.push('not a git repository');
|
|
21
|
+
const worktreeRoot = root ? defaultWorktreeRoot(root) : '';
|
|
22
|
+
const ownerRepo = await ghOut(gh, [
|
|
23
|
+
'repo',
|
|
24
|
+
'view',
|
|
25
|
+
'--json',
|
|
26
|
+
'nameWithOwner',
|
|
27
|
+
'--jq',
|
|
28
|
+
'.nameWithOwner',
|
|
29
|
+
]);
|
|
30
|
+
if (!ownerRepo)
|
|
31
|
+
warnings.push('could not resolve the GitHub repo from the working directory');
|
|
32
|
+
let agentUser = env.AI_LOOP_AGENT?.trim() || (root ? await configuredAgentUser(root) : '') || '';
|
|
33
|
+
// LOGIN is the injection boundary — the login goes into an API path.
|
|
34
|
+
if (agentUser &&
|
|
35
|
+
(!ownerRepo ||
|
|
36
|
+
!LOGIN.test(agentUser) ||
|
|
37
|
+
!(await gh(['api', `repos/${ownerRepo}/assignees/${agentUser}`, '--silent'])).ok)) {
|
|
38
|
+
warnings.push(`agentUser '${agentUser}' is not an assignable collaborator — assigning nothing`);
|
|
39
|
+
agentUser = '';
|
|
40
|
+
}
|
|
41
|
+
const humanUser = ownerRepo
|
|
42
|
+
? await ghOut(gh, [
|
|
43
|
+
'api',
|
|
44
|
+
`repos/${ownerRepo}`,
|
|
45
|
+
'--jq',
|
|
46
|
+
'if .owner.type == "User" then .owner.login else "" end',
|
|
47
|
+
])
|
|
48
|
+
: '';
|
|
49
|
+
const me = await ghOut(gh, ['api', 'user', '--jq', '.login']);
|
|
50
|
+
return { root, worktreeRoot, ownerRepo, agentUser, humanUser, me, warnings };
|
|
51
|
+
}
|
|
52
|
+
const VARS = [
|
|
53
|
+
['ROOT', 'root'],
|
|
54
|
+
['WT_ROOT', 'worktreeRoot'],
|
|
55
|
+
['OWNER_REPO', 'ownerRepo'],
|
|
56
|
+
['AGENT_USER', 'agentUser'],
|
|
57
|
+
['HUMAN_USER', 'humanUser'],
|
|
58
|
+
['ME', 'me'],
|
|
59
|
+
];
|
|
60
|
+
/** `KEY='value'` lines, single-quoted so `eval "$(… loop env)"` is safe. */
|
|
61
|
+
export function toShell(env) {
|
|
62
|
+
return VARS.map(([name, key]) => `${name}='${env[key].replaceAll("'", `'\\''`)}'`).join('\n');
|
|
63
|
+
}
|
|
64
|
+
export async function loopEnvCommand(options) {
|
|
65
|
+
const env = await resolveLoopEnv(options);
|
|
66
|
+
if (options.json) {
|
|
67
|
+
console.log(JSON.stringify(env, null, 2));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
for (const w of env.warnings)
|
|
71
|
+
console.error(`⚠ ${w}`);
|
|
72
|
+
console.log(toShell(env));
|
|
73
|
+
}
|
|
74
|
+
// Nothing downstream works without both — the skill bails on either.
|
|
75
|
+
process.exitCode = env.root && env.ownerRepo ? 0 : 1;
|
|
76
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { Command } from 'commander';
|
|
|
5
5
|
import fs from 'fs-extra';
|
|
6
6
|
import { doctorCommand } from './commands/doctor.js';
|
|
7
7
|
import { fixCommand } from './commands/fix.js';
|
|
8
|
+
import { loopEnvCommand } from './commands/loop-env.js';
|
|
8
9
|
import { loopGuardCommand } from './commands/loop-guard.js';
|
|
9
10
|
import { setupProject } from './commands/setup.js';
|
|
10
11
|
import { copyPreset, PRESETS } from './utils/copy-preset.js';
|
|
@@ -370,6 +371,14 @@ loop
|
|
|
370
371
|
' 1 repair was attempted and failed; the root is still bare — halt the tick\n' +
|
|
371
372
|
' 2 root is not a repairable main checkout (bare clone, linked worktree, or not a repo) — halt the tick\n')
|
|
372
373
|
.action(loopGuardCommand);
|
|
374
|
+
loop
|
|
375
|
+
.command('env')
|
|
376
|
+
.description('🧭 Resolve ROOT, WT_ROOT, OWNER_REPO, AGENT_USER, HUMAN_USER and ME once')
|
|
377
|
+
.option('-d, --dir <path>', 'Directory to resolve from — main checkout or any worktree', process.cwd())
|
|
378
|
+
.option('--json', 'Emit machine-readable JSON output')
|
|
379
|
+
.addHelpText('after', "\nWithout --json, prints KEY='value' lines for eval. Empty means none.\n" +
|
|
380
|
+
'Exits 1 when the checkout or its GitHub repo cannot be resolved.\n')
|
|
381
|
+
.action(loopEnvCommand);
|
|
373
382
|
program.hook('preAction', async (_, actionCommand) => {
|
|
374
383
|
const name = actionCommand.name();
|
|
375
384
|
if (name === 'setup' || name === 'doctor' || name === 'fix') {
|
package/package.json
CHANGED