@aipper/aiws 0.0.21 → 0.0.23
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 +1 -1
- package/package.json +2 -2
- package/src/cli.js +5 -2
- package/src/commands/change.js +45 -8
- package/src/commands/update.js +20 -0
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ aiws codex <install-skills|status-skills|uninstall-skills|install-prompts|status
|
|
|
47
47
|
|
|
48
48
|
## Codex(repo skills 优先)
|
|
49
49
|
|
|
50
|
-
- `aiws init .` 会生成 `.agents/skills/`(随仓库共享),在 Codex 中可显式调用(示例):`$ws-preflight` / `$ws-plan` / `$ws-dev` / `$ws-review` / `$ws-commit`
|
|
50
|
+
- `aiws init .` 会生成 `.agents/skills/`(随仓库共享),在 Codex 中可显式调用(示例):`$ws-preflight` / `$ws-plan` / `$p-tasks-plan` / `$ws-handoff` / `$ws-dev` / `$ws-review` / `$ws-commit`
|
|
51
51
|
- 交付收尾(submodules+superproject 分步提交 + 安全合并):`$ws-deliver`
|
|
52
52
|
- 收尾(安全合并回目标分支):`$ws-finish`(底层调用 `aiws change finish`,默认 fast-forward)
|
|
53
53
|
- 可选:安装全局 skills 到 `~/.codex/skills/`(或 `$CODEX_HOME/skills`):
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipper/aiws",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "AI Workspace CLI (init/update/validate) for Claude Code / OpenCode / Codex / iFlow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"aiws": "./bin/aiws.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@aipper/aiws-spec": "0.0.
|
|
10
|
+
"@aipper/aiws-spec": "0.0.23"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"bin",
|
package/src/cli.js
CHANGED
|
@@ -260,6 +260,7 @@ export async function cliMain(argv) {
|
|
|
260
260
|
hooks: { type: "boolean" },
|
|
261
261
|
"no-switch": { type: "boolean" },
|
|
262
262
|
switch: { type: "boolean" },
|
|
263
|
+
"allow-dirty": { type: "boolean" },
|
|
263
264
|
worktree: { type: "boolean" },
|
|
264
265
|
"worktree-dir": { type: "string" },
|
|
265
266
|
submodules: { type: "boolean" },
|
|
@@ -268,7 +269,7 @@ export async function cliMain(argv) {
|
|
|
268
269
|
if (!changeId)
|
|
269
270
|
throw new UserError("change start requires <change-id>", {
|
|
270
271
|
details:
|
|
271
|
-
"Usage: aiws change start <change-id> [--title <title>] [--no-design] [--hooks] [--no-switch] [--switch] [--worktree] [--worktree-dir <path>] [--submodules]",
|
|
272
|
+
"Usage: aiws change start <change-id> [--title <title>] [--no-design] [--hooks] [--no-switch] [--switch] [--allow-dirty] [--worktree] [--worktree-dir <path>] [--submodules]",
|
|
272
273
|
});
|
|
273
274
|
await changeStartCommand({
|
|
274
275
|
changeId,
|
|
@@ -277,6 +278,7 @@ export async function cliMain(argv) {
|
|
|
277
278
|
enableHooks: options.hooks === true,
|
|
278
279
|
noSwitch: options["no-switch"] === true,
|
|
279
280
|
forceSwitch: options.switch === true,
|
|
281
|
+
allowDirty: options["allow-dirty"] === true,
|
|
280
282
|
worktree: options.worktree === true,
|
|
281
283
|
worktreeDir: options["worktree-dir"],
|
|
282
284
|
submodules: options.submodules === true,
|
|
@@ -490,7 +492,7 @@ function printChangeHelp() {
|
|
|
490
492
|
|
|
491
493
|
Usage:
|
|
492
494
|
aiws change list
|
|
493
|
-
aiws change start <change-id> [--title <title>] [--no-design] [--hooks] [--no-switch] [--switch] [--worktree] [--worktree-dir <path>]
|
|
495
|
+
aiws change start <change-id> [--title <title>] [--no-design] [--hooks] [--no-switch] [--switch] [--allow-dirty] [--worktree] [--worktree-dir <path>]
|
|
494
496
|
aiws change finish [change-id] [--into <branch> | --base <branch>]
|
|
495
497
|
aiws change new <change-id> [--title <title>] [--no-design]
|
|
496
498
|
aiws change status [change-id]
|
|
@@ -510,6 +512,7 @@ Notes:
|
|
|
510
512
|
- Execution quality gate first: aiws change validate [change-id] --strict
|
|
511
513
|
(equivalent to running $ws-plan-verify in AI tools).
|
|
512
514
|
- status/next now prioritize quality-gate guidance before coding ($ws-dev).
|
|
515
|
+
- start refuses to switch/create worktree with a dirty working tree (unless --allow-dirty).
|
|
513
516
|
- If .gitmodules exists and you didn't specify --switch/--no-switch/--worktree,
|
|
514
517
|
start will prefer --worktree (fallback: --no-switch) to avoid switching the superproject branch.
|
|
515
518
|
- archive runs strict validation and (by default) requires all tasks checked.
|
package/src/commands/change.js
CHANGED
|
@@ -1417,7 +1417,7 @@ export async function changeNewCommand(options) {
|
|
|
1417
1417
|
/**
|
|
1418
1418
|
* aiws change start
|
|
1419
1419
|
*
|
|
1420
|
-
* @param {{ changeId: string, title?: string, noDesign: boolean, enableHooks: boolean, noSwitch: boolean, forceSwitch: boolean, worktree: boolean, worktreeDir?: string, submodules: boolean }} options
|
|
1420
|
+
* @param {{ changeId: string, title?: string, noDesign: boolean, enableHooks: boolean, noSwitch: boolean, forceSwitch: boolean, allowDirty?: boolean, worktree: boolean, worktreeDir?: string, submodules: boolean }} options
|
|
1421
1421
|
*/
|
|
1422
1422
|
export async function changeStartCommand(options) {
|
|
1423
1423
|
const gitRoot = await resolveGitRoot(process.cwd());
|
|
@@ -1429,6 +1429,7 @@ export async function changeStartCommand(options) {
|
|
|
1429
1429
|
const startFromBranch = await currentBranch(gitRoot);
|
|
1430
1430
|
const branch = `change/${changeId}`;
|
|
1431
1431
|
const branchRef = `refs/heads/${branch}`;
|
|
1432
|
+
const allowDirty = options.allowDirty === true;
|
|
1432
1433
|
|
|
1433
1434
|
if (options.worktree === true && options.noSwitch === true) {
|
|
1434
1435
|
throw new UserError("change start: cannot combine --worktree with --no-switch");
|
|
@@ -1468,6 +1469,24 @@ export async function changeStartCommand(options) {
|
|
|
1468
1469
|
console.error("warn: recommended for submodules: aiws change start <change-id> --worktree");
|
|
1469
1470
|
}
|
|
1470
1471
|
|
|
1472
|
+
if (!allowDirty && (effectiveWorktree || !effectiveNoSwitch)) {
|
|
1473
|
+
const clean = await checkGitClean(gitRoot);
|
|
1474
|
+
if (!clean.clean) {
|
|
1475
|
+
const mode = effectiveWorktree ? "worktree" : "switch";
|
|
1476
|
+
throw new UserError(`Refusing to start change with a dirty working tree (${mode} mode).`, {
|
|
1477
|
+
details:
|
|
1478
|
+
`${clean.details}\n\n` +
|
|
1479
|
+
`Why: starting in ${mode} mode changes your checkout context; uncommitted changes may not appear where you expect.\n` +
|
|
1480
|
+
"Fix: commit first, then retry:\n" +
|
|
1481
|
+
" git add -A && git commit -m \"wip: save before change start\"\n" +
|
|
1482
|
+
"Fix: or stash:\n" +
|
|
1483
|
+
" git stash\n" +
|
|
1484
|
+
`Alt: prepare artifacts without switching: aiws change start ${changeId} --no-switch\n` +
|
|
1485
|
+
"Alt: proceed anyway (not recommended): add --allow-dirty",
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1471
1490
|
if (effectiveWorktree) {
|
|
1472
1491
|
const prereq = await checkWorktreePrereqs(gitRoot);
|
|
1473
1492
|
if (!prereq.ok) {
|
|
@@ -1545,12 +1564,30 @@ export async function changeStartCommand(options) {
|
|
|
1545
1564
|
await maybeRecordBaseBranch(changeDir, startFromBranch, branch);
|
|
1546
1565
|
}
|
|
1547
1566
|
|
|
1548
|
-
if
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1567
|
+
// Worktree mode: if this repo declares submodules, always initialize them in the new worktree.
|
|
1568
|
+
// --submodules is now implicit (kept for backwards compat but has no effect).
|
|
1569
|
+
if (hasGitmodules) {
|
|
1570
|
+
const hasEntries = await runCommand("git", ["config", "--file", ".gitmodules", "--get-regexp", "^submodule\\..*\\.path$"], { cwd: destKey }).then(
|
|
1571
|
+
(r) => r.code === 0 && String(r.stdout || "").trim().length > 0
|
|
1572
|
+
);
|
|
1573
|
+
if (hasEntries) {
|
|
1574
|
+
console.error("info: .gitmodules detected; initializing submodules in the new worktree.");
|
|
1575
|
+
const sm = await runCommand("git", ["submodule", "update", "--init", "--recursive"], { cwd: destKey });
|
|
1576
|
+
if (sm.code !== 0) throw new UserError("Failed to init/update submodules in worktree.", { details: sm.stderr || sm.stdout });
|
|
1577
|
+
|
|
1578
|
+
// Sanity check: fail fast if anything remains uninitialized or conflicted.
|
|
1579
|
+
const st = await runCommand("git", ["submodule", "status", "--recursive"], { cwd: destKey });
|
|
1580
|
+
const lines = String(st.stdout || "")
|
|
1581
|
+
.split("\n")
|
|
1582
|
+
.map((l) => l.trimEnd())
|
|
1583
|
+
.filter(Boolean);
|
|
1584
|
+
const bad = lines.filter((l) => l.startsWith("-") || l.startsWith("U"));
|
|
1585
|
+
if (bad.length > 0) {
|
|
1586
|
+
throw new UserError("Submodules not fully initialized in worktree.", {
|
|
1587
|
+
details: `${bad.slice(0, 20).join("\n")}${bad.length > 20 ? "\n..." : ""}\n\nHint: run in the worktree:\n git submodule update --init --recursive`,
|
|
1588
|
+
});
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1554
1591
|
}
|
|
1555
1592
|
|
|
1556
1593
|
if (options.enableHooks) {
|
|
@@ -1562,7 +1599,7 @@ export async function changeStartCommand(options) {
|
|
|
1562
1599
|
worktree: destKey,
|
|
1563
1600
|
branch,
|
|
1564
1601
|
from_branch: startFromBranch || "",
|
|
1565
|
-
submodules:
|
|
1602
|
+
submodules: true,
|
|
1566
1603
|
hooks: options.enableHooks === true,
|
|
1567
1604
|
});
|
|
1568
1605
|
|
package/src/commands/update.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { loadTemplate } from "../spec.js";
|
|
3
4
|
import { loadAiwsPackage } from "../aiws-package.js";
|
|
@@ -59,13 +60,32 @@ export async function updateCommand(options) {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
const removeFiles = (update.remove || []).map(normalizeRel);
|
|
64
|
+
|
|
62
65
|
const backup = new BackupSession({ workspaceRoot, operation: "update" });
|
|
63
66
|
// Backup every file we might touch (including missing, for rollback deletions).
|
|
64
67
|
await backup.recordFile(".aiws/manifest.json", { recordMissing: true });
|
|
65
68
|
for (const f of replaceFiles) await backup.recordFile(f, { recordMissing: true });
|
|
66
69
|
for (const f of Object.keys(managedBlocks)) await backup.recordFile(f, { recordMissing: true });
|
|
70
|
+
for (const f of removeFiles) await backup.recordFile(f);
|
|
67
71
|
await backup.finalize({ extra: { template_id: tpl.templateId } });
|
|
68
72
|
|
|
73
|
+
// Remove deprecated files (backed up above; restorable via `aiws rollback`).
|
|
74
|
+
let removedCount = 0;
|
|
75
|
+
for (const rel of removeFiles) {
|
|
76
|
+
if (!rel) continue;
|
|
77
|
+
const abs = joinRel(workspaceRoot, rel);
|
|
78
|
+
if (await pathExists(abs)) {
|
|
79
|
+
await fs.rm(abs, { force: true });
|
|
80
|
+
removedCount++;
|
|
81
|
+
// Remove empty parent directory (safe: rmdir fails on non-empty).
|
|
82
|
+
try { await fs.rmdir(path.dirname(abs)); } catch { /* non-empty or gone */ }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (removedCount > 0) {
|
|
86
|
+
console.log(` removed ${removedCount} deprecated file(s)`);
|
|
87
|
+
}
|
|
88
|
+
|
|
69
89
|
// Replace files (except manifest, generated later).
|
|
70
90
|
for (const rel of replaceFiles) {
|
|
71
91
|
if (!rel || rel === ".aiws/manifest.json") continue;
|