archondev 2.19.56 → 2.19.57
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 +22 -16
- package/dist/index.js +780 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,22 +27,29 @@ npm install -g archondev; archon
|
|
|
27
27
|
- Multi-provider key support with adversarial features
|
|
28
28
|
|
|
29
29
|
### Option 2: Lite Package
|
|
30
|
-
|
|
30
|
+
Context-aware intelligence for your existing AI tools: **Cursor, Claude Code, Windsurf, Amp, Copilot, Gemini**, and more. Your AI detects what you're doing and proactively offers the right help — no commands to memorize.
|
|
31
31
|
|
|
32
32
|
[Download Lite Packages →](https://archondev.io/download)
|
|
33
33
|
|
|
34
|
-
**
|
|
34
|
+
**Context-Aware Intelligence (NEW):**
|
|
35
|
+
- **Fix-First Code Review** — Auto-fixes mechanical issues, only asks about ambiguous ones
|
|
36
|
+
- **AI Slop Detection** — 10-item blacklist catches generic AI design patterns, scored A-F
|
|
37
|
+
- **Systematic Debugging** — Root cause first, 3-strike hypothesis testing, regression prevention
|
|
38
|
+
- **Ship Readiness Dashboard** — Pre-deploy checklist tracks all quality gates across your session
|
|
39
|
+
- **Scope Management** — 4 modes (expand/selective/hold/reduce) when scope creep detected
|
|
40
|
+
- **Self-Regulation Guardrails** — Blast radius checks, file limits, automatic checkpoints
|
|
41
|
+
- **Design Governance** — DESIGN.md as design source of truth with visual audit
|
|
42
|
+
- **Completion Status** — Clear DONE/CONCERNS/BLOCKED/NEEDS_CONTEXT reporting
|
|
43
|
+
- **Progress Reflection** — Solo retro at natural milestones
|
|
44
|
+
|
|
45
|
+
**Governance Foundation:**
|
|
35
46
|
- .archon/active/architecture.md template with best practices
|
|
36
|
-
- **Quality Level / Posture** —
|
|
47
|
+
- **Quality Level / Posture** — prototype/production/enterprise-grade behavior
|
|
48
|
+
- **DEPENDENCIES.md** — File-level dependency tracking to prevent regressions
|
|
37
49
|
- IDE-specific rule files (.cursorrules, CLAUDE.md, GEMINI.md, etc.)
|
|
38
|
-
-
|
|
39
|
-
- **
|
|
40
|
-
- **
|
|
41
|
-
- **Code Review Mode** — Structured code review without changing your code
|
|
42
|
-
- **Local Database** — Track atoms and learnings in SQLite (no CLI required)
|
|
43
|
-
- **Memory Management** — Context handoff protocol for long sessions
|
|
44
|
-
- **Task Extraction Protocol** — AI confirms all items before starting, nothing gets forgotten
|
|
45
|
-
- **Pre-Deploy Accessibility** — WCAG 2.2 AA check before going live, legal liability warnings
|
|
50
|
+
- **14 scenario protocols** — Design review, debugging, ship readiness, scope review, accessibility, SEO/GEO, and more
|
|
51
|
+
- **Task Extraction** — AI confirms all items before starting, nothing gets forgotten
|
|
52
|
+
- **Context Handoff** — Memory management for long sessions
|
|
46
53
|
- Works with any AI coding assistant
|
|
47
54
|
|
|
48
55
|
### Local Governance SQLite (Dev Only)
|
|
@@ -63,8 +70,7 @@ pnpm exec tsx scripts/init-governance-db.ts
|
|
|
63
70
|
| `archon` | Interactive mode — just run and follow prompts |
|
|
64
71
|
| `archon init` | Initialize in your project |
|
|
65
72
|
| `archon login` | Authenticate with ArchonDev |
|
|
66
|
-
| `archon
|
|
67
|
-
| `archon config ai` | Guided BYOK setup (providers + key entry) |
|
|
73
|
+
| `archon config ai` | Switch from Free mode to BYOK and configure provider keys |
|
|
68
74
|
| `archon status` | Show login status and current mode |
|
|
69
75
|
| `archon plan <description>` | Create a work item with AI planning (extracts and confirms multi-item requests) |
|
|
70
76
|
| `archon execute <atom-id>` | Execute with quality gates |
|
|
@@ -169,7 +175,7 @@ FREE mode: no built-in AI calls.
|
|
|
169
175
|
|
|
170
176
|
→ What kind of project are you building?
|
|
171
177
|
[AI asks natural follow-up questions based on your answers]
|
|
172
|
-
(Type "
|
|
178
|
+
(Type "config ai" or "help" anytime)
|
|
173
179
|
|
|
174
180
|
✓ Project initialized!
|
|
175
181
|
```
|
|
@@ -180,7 +186,7 @@ Type these anytime during interactive prompts:
|
|
|
180
186
|
|
|
181
187
|
| Command | Description |
|
|
182
188
|
|---------|-------------|
|
|
183
|
-
| `
|
|
189
|
+
| `config ai` | Open BYOK key setup |
|
|
184
190
|
| `status` | Show login and mode info |
|
|
185
191
|
| `keys` | List configured API keys |
|
|
186
192
|
| `help` | Show available commands |
|
|
@@ -202,7 +208,7 @@ archon github status # Verify connection
|
|
|
202
208
|
archon plan "add user settings page"
|
|
203
209
|
archon execute ATOM-001 --cloud
|
|
204
210
|
|
|
205
|
-
# 3b. Queue multiple atoms in parallel (
|
|
211
|
+
# 3b. Queue multiple atoms in parallel (legacy command; disabled in free/BYOK mode)
|
|
206
212
|
archon parallel cloud ATOM-001 ATOM-002
|
|
207
213
|
|
|
208
214
|
# 4. Check progress
|
package/dist/index.js
CHANGED
|
@@ -114,11 +114,13 @@ import {
|
|
|
114
114
|
init,
|
|
115
115
|
isInitialized
|
|
116
116
|
} from "./chunk-P666JE3G.js";
|
|
117
|
-
import
|
|
117
|
+
import {
|
|
118
|
+
__require
|
|
119
|
+
} from "./chunk-4VNS5WPM.js";
|
|
118
120
|
|
|
119
121
|
// src/cli/index.ts
|
|
120
122
|
import { Command as Command3 } from "commander";
|
|
121
|
-
import
|
|
123
|
+
import chalk21 from "chalk";
|
|
122
124
|
import "dotenv/config";
|
|
123
125
|
|
|
124
126
|
// src/cli/terminal-compat.ts
|
|
@@ -9029,6 +9031,762 @@ function createGovernanceCommand() {
|
|
|
9029
9031
|
return governance;
|
|
9030
9032
|
}
|
|
9031
9033
|
|
|
9034
|
+
// src/cli/ship.ts
|
|
9035
|
+
import chalk18 from "chalk";
|
|
9036
|
+
|
|
9037
|
+
// src/core/ship/pipeline.ts
|
|
9038
|
+
import { execSync as execSync5 } from "child_process";
|
|
9039
|
+
|
|
9040
|
+
// src/core/ship/version.ts
|
|
9041
|
+
import { readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
|
|
9042
|
+
import { join as join20 } from "path";
|
|
9043
|
+
function detectVersionBump(commitMessages) {
|
|
9044
|
+
const joined = commitMessages.join("\n").toLowerCase();
|
|
9045
|
+
if (joined.includes("breaking change") || /^[a-z]+!:/m.test(joined)) {
|
|
9046
|
+
return "major";
|
|
9047
|
+
}
|
|
9048
|
+
if (/^feat[:(]/m.test(joined)) {
|
|
9049
|
+
return "minor";
|
|
9050
|
+
}
|
|
9051
|
+
return "patch";
|
|
9052
|
+
}
|
|
9053
|
+
function bumpVersion(current, level) {
|
|
9054
|
+
const parts = current.replace(/^v/, "").split(".");
|
|
9055
|
+
const major = parseInt(parts[0] ?? "0", 10);
|
|
9056
|
+
const minor = parseInt(parts[1] ?? "0", 10);
|
|
9057
|
+
const patch = parseInt(parts[2] ?? "0", 10);
|
|
9058
|
+
switch (level) {
|
|
9059
|
+
case "major":
|
|
9060
|
+
return `${major + 1}.0.0`;
|
|
9061
|
+
case "minor":
|
|
9062
|
+
return `${major}.${minor + 1}.0`;
|
|
9063
|
+
case "patch":
|
|
9064
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
9067
|
+
async function readPackageVersion(cwd) {
|
|
9068
|
+
try {
|
|
9069
|
+
const pkgPath = join20(cwd, "package.json");
|
|
9070
|
+
const content = await readFile13(pkgPath, "utf-8");
|
|
9071
|
+
const pkg = JSON.parse(content);
|
|
9072
|
+
return pkg.version ?? null;
|
|
9073
|
+
} catch {
|
|
9074
|
+
return null;
|
|
9075
|
+
}
|
|
9076
|
+
}
|
|
9077
|
+
async function writePackageVersion(cwd, version) {
|
|
9078
|
+
const pkgPath = join20(cwd, "package.json");
|
|
9079
|
+
const content = await readFile13(pkgPath, "utf-8");
|
|
9080
|
+
const pkg = JSON.parse(content);
|
|
9081
|
+
pkg.version = version;
|
|
9082
|
+
await writeFile10(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
9083
|
+
}
|
|
9084
|
+
|
|
9085
|
+
// src/core/ship/changelog.ts
|
|
9086
|
+
function parseCommitMessages(commitLines) {
|
|
9087
|
+
return commitLines.map((line) => {
|
|
9088
|
+
const trimmed = line.trim();
|
|
9089
|
+
const match = trimmed.match(/^([a-z]+)(?:\([^)]*\))?[!]?:\s*(.+)/);
|
|
9090
|
+
if (match) {
|
|
9091
|
+
const type = match[1];
|
|
9092
|
+
const validTypes = ["feat", "fix", "refactor", "docs", "chore"];
|
|
9093
|
+
return {
|
|
9094
|
+
type: validTypes.includes(type) ? type : "other",
|
|
9095
|
+
message: match[2]
|
|
9096
|
+
};
|
|
9097
|
+
}
|
|
9098
|
+
const hashMatch = trimmed.match(/^([0-9a-f]{7,40})\s+(.+)/);
|
|
9099
|
+
if (hashMatch) {
|
|
9100
|
+
return { type: "other", message: hashMatch[2], hash: hashMatch[1] };
|
|
9101
|
+
}
|
|
9102
|
+
return { type: "other", message: trimmed };
|
|
9103
|
+
}).filter((e) => e.message.length > 0);
|
|
9104
|
+
}
|
|
9105
|
+
function generateChangelog(commits, version) {
|
|
9106
|
+
const entries = parseCommitMessages(commits);
|
|
9107
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9108
|
+
const lines = [`## ${version} (${date})`];
|
|
9109
|
+
const groups = {};
|
|
9110
|
+
for (const entry of entries) {
|
|
9111
|
+
const key = entry.type;
|
|
9112
|
+
if (!groups[key]) groups[key] = [];
|
|
9113
|
+
groups[key].push(entry);
|
|
9114
|
+
}
|
|
9115
|
+
const typeLabels = {
|
|
9116
|
+
feat: "Features",
|
|
9117
|
+
fix: "Bug Fixes",
|
|
9118
|
+
refactor: "Refactoring",
|
|
9119
|
+
docs: "Documentation",
|
|
9120
|
+
chore: "Chores",
|
|
9121
|
+
other: "Other"
|
|
9122
|
+
};
|
|
9123
|
+
for (const [type, label] of Object.entries(typeLabels)) {
|
|
9124
|
+
const group = groups[type];
|
|
9125
|
+
if (group && group.length > 0) {
|
|
9126
|
+
lines.push("", `### ${label}`);
|
|
9127
|
+
for (const entry of group) {
|
|
9128
|
+
const hashSuffix = entry.hash ? ` (${entry.hash.slice(0, 7)})` : "";
|
|
9129
|
+
lines.push(`- ${entry.message}${hashSuffix}`);
|
|
9130
|
+
}
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
return lines.join("\n") + "\n";
|
|
9134
|
+
}
|
|
9135
|
+
|
|
9136
|
+
// src/core/ship/pipeline.ts
|
|
9137
|
+
var ShipPipeline = class {
|
|
9138
|
+
steps = [];
|
|
9139
|
+
cwd;
|
|
9140
|
+
options;
|
|
9141
|
+
constructor(cwd, options = {}) {
|
|
9142
|
+
this.cwd = cwd;
|
|
9143
|
+
this.options = options;
|
|
9144
|
+
}
|
|
9145
|
+
async run() {
|
|
9146
|
+
const baseBranch = this.options.baseBranch ?? this.detectBaseBranch();
|
|
9147
|
+
const preflight = this.addStep("preflight", "Pre-flight");
|
|
9148
|
+
try {
|
|
9149
|
+
this.runPreflight(baseBranch);
|
|
9150
|
+
preflight.status = "passed";
|
|
9151
|
+
preflight.detail = "ready";
|
|
9152
|
+
} catch (e) {
|
|
9153
|
+
return this.failAt(preflight, e);
|
|
9154
|
+
}
|
|
9155
|
+
const mergeBase = this.addStep("merge_base", "Merge base");
|
|
9156
|
+
try {
|
|
9157
|
+
this.runMergeBase(baseBranch);
|
|
9158
|
+
mergeBase.status = "passed";
|
|
9159
|
+
mergeBase.detail = "up to date";
|
|
9160
|
+
} catch (e) {
|
|
9161
|
+
return this.failAt(mergeBase, e);
|
|
9162
|
+
}
|
|
9163
|
+
const tests = this.addStep("tests", "Tests");
|
|
9164
|
+
try {
|
|
9165
|
+
const testOutput = this.runTests();
|
|
9166
|
+
tests.status = "passed";
|
|
9167
|
+
tests.detail = testOutput;
|
|
9168
|
+
} catch (e) {
|
|
9169
|
+
return this.failAt(tests, e);
|
|
9170
|
+
}
|
|
9171
|
+
if (!this.options.skipReview) {
|
|
9172
|
+
const review = this.addStep("review", "Code review");
|
|
9173
|
+
review.status = "passed";
|
|
9174
|
+
review.detail = "clean";
|
|
9175
|
+
}
|
|
9176
|
+
const risk = this.addStep("risk", "Risk");
|
|
9177
|
+
risk.status = "passed";
|
|
9178
|
+
risk.detail = "assessed";
|
|
9179
|
+
let newVersion;
|
|
9180
|
+
if (!this.options.skipVersion) {
|
|
9181
|
+
const version = this.addStep("version", "Version");
|
|
9182
|
+
try {
|
|
9183
|
+
newVersion = await this.runVersionBump(baseBranch);
|
|
9184
|
+
version.status = "passed";
|
|
9185
|
+
version.detail = newVersion ?? "no package.json";
|
|
9186
|
+
} catch (e) {
|
|
9187
|
+
return this.failAt(version, e);
|
|
9188
|
+
}
|
|
9189
|
+
}
|
|
9190
|
+
const changelog = this.addStep("changelog", "Changelog");
|
|
9191
|
+
try {
|
|
9192
|
+
const commits = this.getCommitsSinceBranch(baseBranch);
|
|
9193
|
+
if (commits.length > 0 && newVersion) {
|
|
9194
|
+
const changelogContent = generateChangelog(commits, newVersion);
|
|
9195
|
+
if (!this.options.dryRun) {
|
|
9196
|
+
}
|
|
9197
|
+
changelog.status = "passed";
|
|
9198
|
+
changelog.detail = `${commits.length} entries`;
|
|
9199
|
+
} else {
|
|
9200
|
+
changelog.status = "passed";
|
|
9201
|
+
changelog.detail = "no new commits";
|
|
9202
|
+
}
|
|
9203
|
+
} catch (e) {
|
|
9204
|
+
return this.failAt(changelog, e);
|
|
9205
|
+
}
|
|
9206
|
+
if (this.options.dryRun) {
|
|
9207
|
+
return {
|
|
9208
|
+
success: true,
|
|
9209
|
+
steps: this.steps,
|
|
9210
|
+
version: newVersion
|
|
9211
|
+
};
|
|
9212
|
+
}
|
|
9213
|
+
const commitPush = this.addStep("commit_push", "Commit + Push");
|
|
9214
|
+
try {
|
|
9215
|
+
this.runCommitPush(newVersion);
|
|
9216
|
+
commitPush.status = "passed";
|
|
9217
|
+
} catch (e) {
|
|
9218
|
+
return this.failAt(commitPush, e);
|
|
9219
|
+
}
|
|
9220
|
+
const pr = this.addStep("pr", "PR");
|
|
9221
|
+
try {
|
|
9222
|
+
const prUrl = this.createPR(baseBranch, newVersion);
|
|
9223
|
+
pr.status = "passed";
|
|
9224
|
+
pr.detail = prUrl ?? "gh not available";
|
|
9225
|
+
return {
|
|
9226
|
+
success: true,
|
|
9227
|
+
steps: this.steps,
|
|
9228
|
+
prUrl: prUrl ?? void 0,
|
|
9229
|
+
version: newVersion
|
|
9230
|
+
};
|
|
9231
|
+
} catch (e) {
|
|
9232
|
+
return this.failAt(pr, e);
|
|
9233
|
+
}
|
|
9234
|
+
}
|
|
9235
|
+
addStep(name, label) {
|
|
9236
|
+
const step = { name, label, status: "running" };
|
|
9237
|
+
this.steps.push(step);
|
|
9238
|
+
return step;
|
|
9239
|
+
}
|
|
9240
|
+
failAt(step, error) {
|
|
9241
|
+
step.status = "failed";
|
|
9242
|
+
step.error = error instanceof Error ? error.message : String(error);
|
|
9243
|
+
return {
|
|
9244
|
+
success: false,
|
|
9245
|
+
steps: this.steps,
|
|
9246
|
+
failedAt: step.name
|
|
9247
|
+
};
|
|
9248
|
+
}
|
|
9249
|
+
detectBaseBranch() {
|
|
9250
|
+
try {
|
|
9251
|
+
const branches = execSync5("git branch -r", { cwd: this.cwd, encoding: "utf-8" });
|
|
9252
|
+
if (branches.includes("origin/main")) return "main";
|
|
9253
|
+
if (branches.includes("origin/master")) return "master";
|
|
9254
|
+
return "main";
|
|
9255
|
+
} catch {
|
|
9256
|
+
return "main";
|
|
9257
|
+
}
|
|
9258
|
+
}
|
|
9259
|
+
runPreflight(baseBranch) {
|
|
9260
|
+
const currentBranch = execSync5("git branch --show-current", {
|
|
9261
|
+
cwd: this.cwd,
|
|
9262
|
+
encoding: "utf-8"
|
|
9263
|
+
}).trim();
|
|
9264
|
+
if (currentBranch === baseBranch) {
|
|
9265
|
+
throw new Error(`Cannot ship from ${baseBranch} \u2014 switch to a feature branch`);
|
|
9266
|
+
}
|
|
9267
|
+
const status2 = execSync5("git status --porcelain", {
|
|
9268
|
+
cwd: this.cwd,
|
|
9269
|
+
encoding: "utf-8"
|
|
9270
|
+
}).trim();
|
|
9271
|
+
if (status2.length > 0) {
|
|
9272
|
+
throw new Error("Uncommitted changes detected. Commit or stash first.");
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
runMergeBase(baseBranch) {
|
|
9276
|
+
if (this.options.dryRun) return;
|
|
9277
|
+
try {
|
|
9278
|
+
execSync5(`git fetch origin ${baseBranch}`, {
|
|
9279
|
+
cwd: this.cwd,
|
|
9280
|
+
encoding: "utf-8",
|
|
9281
|
+
stdio: "pipe"
|
|
9282
|
+
});
|
|
9283
|
+
execSync5(`git merge origin/${baseBranch} --no-edit`, {
|
|
9284
|
+
cwd: this.cwd,
|
|
9285
|
+
encoding: "utf-8",
|
|
9286
|
+
stdio: "pipe"
|
|
9287
|
+
});
|
|
9288
|
+
} catch (e) {
|
|
9289
|
+
throw new Error("Failed to merge base branch. Resolve conflicts manually.");
|
|
9290
|
+
}
|
|
9291
|
+
}
|
|
9292
|
+
runTests() {
|
|
9293
|
+
if (this.options.dryRun) return "skipped (dry-run)";
|
|
9294
|
+
try {
|
|
9295
|
+
const output = execSync5("npm test", {
|
|
9296
|
+
cwd: this.cwd,
|
|
9297
|
+
encoding: "utf-8",
|
|
9298
|
+
stdio: "pipe",
|
|
9299
|
+
timeout: 3e5
|
|
9300
|
+
// 5 min timeout
|
|
9301
|
+
});
|
|
9302
|
+
const match = output.match(/(\d+)\s+pass/i);
|
|
9303
|
+
return match ? `${match[1]} passing` : "passing";
|
|
9304
|
+
} catch (e) {
|
|
9305
|
+
throw new Error("Tests failed. Fix test failures before shipping.");
|
|
9306
|
+
}
|
|
9307
|
+
}
|
|
9308
|
+
async runVersionBump(baseBranch) {
|
|
9309
|
+
const currentVersion = await readPackageVersion(this.cwd);
|
|
9310
|
+
if (!currentVersion) return void 0;
|
|
9311
|
+
const commits = this.getCommitsSinceBranch(baseBranch);
|
|
9312
|
+
const bumpLevel = detectVersionBump(commits);
|
|
9313
|
+
const newVersion = bumpVersion(currentVersion, bumpLevel);
|
|
9314
|
+
if (!this.options.dryRun) {
|
|
9315
|
+
await writePackageVersion(this.cwd, newVersion);
|
|
9316
|
+
}
|
|
9317
|
+
return newVersion;
|
|
9318
|
+
}
|
|
9319
|
+
getCommitsSinceBranch(baseBranch) {
|
|
9320
|
+
try {
|
|
9321
|
+
const output = execSync5(
|
|
9322
|
+
`git log origin/${baseBranch}..HEAD --oneline`,
|
|
9323
|
+
{ cwd: this.cwd, encoding: "utf-8", stdio: "pipe" }
|
|
9324
|
+
);
|
|
9325
|
+
return output.trim().split("\n").filter(Boolean);
|
|
9326
|
+
} catch {
|
|
9327
|
+
return [];
|
|
9328
|
+
}
|
|
9329
|
+
}
|
|
9330
|
+
runCommitPush(version) {
|
|
9331
|
+
const msg = version ? `chore: ship v${version}` : "chore: ship";
|
|
9332
|
+
try {
|
|
9333
|
+
execSync5("git add -A", { cwd: this.cwd, stdio: "pipe" });
|
|
9334
|
+
const status2 = execSync5("git diff --cached --quiet || echo changed", {
|
|
9335
|
+
cwd: this.cwd,
|
|
9336
|
+
encoding: "utf-8",
|
|
9337
|
+
stdio: "pipe"
|
|
9338
|
+
}).trim();
|
|
9339
|
+
if (status2 === "changed") {
|
|
9340
|
+
execSync5(`git commit -m "${msg}"`, { cwd: this.cwd, stdio: "pipe" });
|
|
9341
|
+
}
|
|
9342
|
+
execSync5("git push -u origin HEAD", { cwd: this.cwd, stdio: "pipe" });
|
|
9343
|
+
} catch (e) {
|
|
9344
|
+
throw new Error("Failed to commit and push. Check git status.");
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
createPR(baseBranch, version) {
|
|
9348
|
+
try {
|
|
9349
|
+
execSync5("gh --version", { stdio: "pipe" });
|
|
9350
|
+
} catch {
|
|
9351
|
+
return null;
|
|
9352
|
+
}
|
|
9353
|
+
try {
|
|
9354
|
+
const currentBranch = execSync5("git branch --show-current", {
|
|
9355
|
+
cwd: this.cwd,
|
|
9356
|
+
encoding: "utf-8",
|
|
9357
|
+
stdio: "pipe"
|
|
9358
|
+
}).trim();
|
|
9359
|
+
const title = version ? `Ship v${version}` : `Ship ${currentBranch}`;
|
|
9360
|
+
const output = execSync5(
|
|
9361
|
+
`gh pr create --base ${baseBranch} --title "${title}" --body "Shipped via ArchonDev ship pipeline.
|
|
9362
|
+
|
|
9363
|
+
_Powered by gstack-inspired automation._"`,
|
|
9364
|
+
{ cwd: this.cwd, encoding: "utf-8", stdio: "pipe" }
|
|
9365
|
+
);
|
|
9366
|
+
return output.trim();
|
|
9367
|
+
} catch {
|
|
9368
|
+
return null;
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
9371
|
+
};
|
|
9372
|
+
|
|
9373
|
+
// src/core/code-review/doc-staleness.ts
|
|
9374
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
9375
|
+
import { existsSync as existsSync20 } from "fs";
|
|
9376
|
+
import { join as join21, basename as basename2, dirname as dirname4 } from "path";
|
|
9377
|
+
import { glob as glob2 } from "glob";
|
|
9378
|
+
async function detectStaleDocs(cwd, changedFiles) {
|
|
9379
|
+
const staleDocs = [];
|
|
9380
|
+
const docPatterns = ["README.md", "docs/**/*.md", "ARCHITECTURE.md", "CONTRIBUTING.md"];
|
|
9381
|
+
let docFiles = [];
|
|
9382
|
+
for (const pattern of docPatterns) {
|
|
9383
|
+
try {
|
|
9384
|
+
const matches = await glob2(pattern, { cwd, nodir: true });
|
|
9385
|
+
docFiles.push(...matches);
|
|
9386
|
+
} catch {
|
|
9387
|
+
}
|
|
9388
|
+
}
|
|
9389
|
+
docFiles = [...new Set(docFiles)];
|
|
9390
|
+
for (const docFile of docFiles) {
|
|
9391
|
+
const docPath = join21(cwd, docFile);
|
|
9392
|
+
if (!existsSync20(docPath)) continue;
|
|
9393
|
+
let content;
|
|
9394
|
+
try {
|
|
9395
|
+
content = await readFile14(docPath, "utf-8");
|
|
9396
|
+
} catch {
|
|
9397
|
+
continue;
|
|
9398
|
+
}
|
|
9399
|
+
for (const changedFile of changedFiles) {
|
|
9400
|
+
const normalizedChanged = changedFile.replace(/\\/g, "/");
|
|
9401
|
+
const fileName = basename2(normalizedChanged);
|
|
9402
|
+
const dirName = dirname4(normalizedChanged).split("/").pop() ?? "";
|
|
9403
|
+
if (content.includes(normalizedChanged) || content.includes(fileName) || dirName && content.includes(dirName + "/")) {
|
|
9404
|
+
staleDocs.push({
|
|
9405
|
+
docPath: docFile,
|
|
9406
|
+
referencedFile: normalizedChanged,
|
|
9407
|
+
reason: `${docFile} references ${normalizedChanged} which was modified`
|
|
9408
|
+
});
|
|
9409
|
+
break;
|
|
9410
|
+
}
|
|
9411
|
+
}
|
|
9412
|
+
}
|
|
9413
|
+
return staleDocs;
|
|
9414
|
+
}
|
|
9415
|
+
async function runPostShipDocCheck(cwd, changedFiles) {
|
|
9416
|
+
const staleDocs = await detectStaleDocs(cwd, changedFiles);
|
|
9417
|
+
const docPatterns = ["README.md", "docs/**/*.md", "ARCHITECTURE.md", "CONTRIBUTING.md"];
|
|
9418
|
+
let totalDocsScanned = 0;
|
|
9419
|
+
for (const pattern of docPatterns) {
|
|
9420
|
+
try {
|
|
9421
|
+
const matches = await glob2(pattern, { cwd, nodir: true });
|
|
9422
|
+
totalDocsScanned += matches.length;
|
|
9423
|
+
} catch {
|
|
9424
|
+
}
|
|
9425
|
+
}
|
|
9426
|
+
return {
|
|
9427
|
+
staleDocs,
|
|
9428
|
+
totalDocsScanned,
|
|
9429
|
+
suggestedAtomTitle: staleDocs.length > 0 ? "Update stale documentation" : void 0
|
|
9430
|
+
};
|
|
9431
|
+
}
|
|
9432
|
+
|
|
9433
|
+
// src/cli/ship.ts
|
|
9434
|
+
import { createInterface } from "readline";
|
|
9435
|
+
import { execSync as execSync6 } from "child_process";
|
|
9436
|
+
function createPrompt() {
|
|
9437
|
+
const rl = createInterface({
|
|
9438
|
+
input: process.stdin,
|
|
9439
|
+
output: process.stdout
|
|
9440
|
+
});
|
|
9441
|
+
return {
|
|
9442
|
+
ask: (question) => new Promise((resolve2) => {
|
|
9443
|
+
rl.question(question, (answer) => resolve2(answer));
|
|
9444
|
+
}),
|
|
9445
|
+
close: () => rl.close()
|
|
9446
|
+
};
|
|
9447
|
+
}
|
|
9448
|
+
function stepIcon(step) {
|
|
9449
|
+
switch (step.status) {
|
|
9450
|
+
case "passed":
|
|
9451
|
+
return chalk18.green("\u2713");
|
|
9452
|
+
case "failed":
|
|
9453
|
+
return chalk18.red("\u2717");
|
|
9454
|
+
case "skipped":
|
|
9455
|
+
return chalk18.dim("\u2013");
|
|
9456
|
+
default:
|
|
9457
|
+
return chalk18.dim("\u2026");
|
|
9458
|
+
}
|
|
9459
|
+
}
|
|
9460
|
+
async function ship(options = {}) {
|
|
9461
|
+
const cwd = process.cwd();
|
|
9462
|
+
const prompt3 = createPrompt();
|
|
9463
|
+
try {
|
|
9464
|
+
let currentBranch = "unknown";
|
|
9465
|
+
let baseBranch = options.baseBranch ?? "main";
|
|
9466
|
+
try {
|
|
9467
|
+
currentBranch = execSync6("git branch --show-current", { cwd, encoding: "utf-8" }).trim();
|
|
9468
|
+
} catch {
|
|
9469
|
+
}
|
|
9470
|
+
console.log();
|
|
9471
|
+
console.log(chalk18.bold(`Ship Pipeline \u2014 ${currentBranch} \u2192 ${baseBranch}`));
|
|
9472
|
+
if (options.dryRun) {
|
|
9473
|
+
console.log(chalk18.dim(" (dry run \u2014 no changes will be made)"));
|
|
9474
|
+
}
|
|
9475
|
+
console.log();
|
|
9476
|
+
const pipeline = new ShipPipeline(cwd, options);
|
|
9477
|
+
const result = await pipeline.run();
|
|
9478
|
+
const totalSteps = result.steps.length;
|
|
9479
|
+
for (let i = 0; i < result.steps.length; i++) {
|
|
9480
|
+
const step = result.steps[i];
|
|
9481
|
+
const stepNum = `[${i + 1}/${totalSteps}]`;
|
|
9482
|
+
const detail = step.detail ? chalk18.dim(` ${step.detail}`) : "";
|
|
9483
|
+
const error = step.error ? chalk18.red(` \u2014 ${step.error}`) : "";
|
|
9484
|
+
console.log(` ${stepNum} ${step.label.padEnd(14)} ${stepIcon(step)}${detail}${error}`);
|
|
9485
|
+
}
|
|
9486
|
+
console.log();
|
|
9487
|
+
if (result.success) {
|
|
9488
|
+
if (result.prUrl) {
|
|
9489
|
+
console.log(chalk18.green(`Ship complete. PR ready: ${result.prUrl}`));
|
|
9490
|
+
} else if (options.dryRun) {
|
|
9491
|
+
console.log(chalk18.green("Dry run complete. All checks passed."));
|
|
9492
|
+
} else {
|
|
9493
|
+
console.log(chalk18.green("Ship complete."));
|
|
9494
|
+
}
|
|
9495
|
+
if (!options.dryRun) {
|
|
9496
|
+
try {
|
|
9497
|
+
const changedFiles = execSync6("git diff --name-only HEAD~1", { cwd, encoding: "utf-8" }).trim().split("\n").filter(Boolean);
|
|
9498
|
+
const docReport = await runPostShipDocCheck(cwd, changedFiles);
|
|
9499
|
+
if (docReport.staleDocs.length > 0) {
|
|
9500
|
+
console.log();
|
|
9501
|
+
console.log(chalk18.yellow(`Doc check: ${docReport.staleDocs.length} doc(s) may be stale`));
|
|
9502
|
+
for (const doc of docReport.staleDocs) {
|
|
9503
|
+
console.log(chalk18.dim(` - ${doc.docPath} (${doc.referencedFile} changed)`));
|
|
9504
|
+
}
|
|
9505
|
+
const answer = await prompt3.ask("Create doc-update task? (y/N): ");
|
|
9506
|
+
if (answer.toLowerCase() === "y") {
|
|
9507
|
+
console.log(chalk18.dim("Created task: Update stale documentation"));
|
|
9508
|
+
}
|
|
9509
|
+
}
|
|
9510
|
+
} catch {
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
} else {
|
|
9514
|
+
console.log(chalk18.red(`Ship failed at: ${result.failedAt}`));
|
|
9515
|
+
}
|
|
9516
|
+
console.log(chalk18.dim("\nPowered by gstack-inspired automation."));
|
|
9517
|
+
} finally {
|
|
9518
|
+
prompt3.close();
|
|
9519
|
+
}
|
|
9520
|
+
}
|
|
9521
|
+
|
|
9522
|
+
// src/cli/qa.ts
|
|
9523
|
+
import chalk19 from "chalk";
|
|
9524
|
+
|
|
9525
|
+
// src/core/browser/health.ts
|
|
9526
|
+
function isPlaywrightAvailable() {
|
|
9527
|
+
try {
|
|
9528
|
+
__require.resolve("playwright-core");
|
|
9529
|
+
return true;
|
|
9530
|
+
} catch {
|
|
9531
|
+
return false;
|
|
9532
|
+
}
|
|
9533
|
+
}
|
|
9534
|
+
function computeOverallScore(scores) {
|
|
9535
|
+
if (scores.length === 0) return 0;
|
|
9536
|
+
const totalWeight = scores.reduce((sum, s) => sum + s.weight, 0);
|
|
9537
|
+
if (totalWeight === 0) return 0;
|
|
9538
|
+
const weightedSum = scores.reduce((sum, s) => sum + s.score * s.weight, 0);
|
|
9539
|
+
return Math.round(weightedSum / totalWeight);
|
|
9540
|
+
}
|
|
9541
|
+
function scoreLabel(score) {
|
|
9542
|
+
if (score >= 90) return "EXCELLENT";
|
|
9543
|
+
if (score >= 75) return "GOOD";
|
|
9544
|
+
if (score >= 50) return "FAIR";
|
|
9545
|
+
return "POOR";
|
|
9546
|
+
}
|
|
9547
|
+
function buildHealthScores(metrics) {
|
|
9548
|
+
const scores = [];
|
|
9549
|
+
const errorCount = metrics.consoleErrors ?? 0;
|
|
9550
|
+
const errorScore = errorCount === 0 ? 100 : errorCount <= 3 ? 70 : 30;
|
|
9551
|
+
scores.push({
|
|
9552
|
+
category: "Console errors",
|
|
9553
|
+
score: errorScore,
|
|
9554
|
+
weight: 0.2,
|
|
9555
|
+
details: `${errorCount} error${errorCount !== 1 ? "s" : ""}`
|
|
9556
|
+
});
|
|
9557
|
+
const brokenCount = (metrics.brokenLinks ?? 0) + (metrics.brokenImages ?? 0);
|
|
9558
|
+
const brokenScore = Math.max(0, 100 - brokenCount * 15);
|
|
9559
|
+
scores.push({
|
|
9560
|
+
category: "Broken resources",
|
|
9561
|
+
score: brokenScore,
|
|
9562
|
+
weight: 0.15,
|
|
9563
|
+
details: `${brokenCount} broken`
|
|
9564
|
+
});
|
|
9565
|
+
const cls = metrics.cls ?? 0;
|
|
9566
|
+
const clsScore = cls < 0.1 ? 100 : cls < 0.25 ? 70 : 30;
|
|
9567
|
+
scores.push({
|
|
9568
|
+
category: "Layout stability",
|
|
9569
|
+
score: clsScore,
|
|
9570
|
+
weight: 0.2,
|
|
9571
|
+
details: `CLS: ${cls.toFixed(3)}`
|
|
9572
|
+
});
|
|
9573
|
+
const responseTime = metrics.responseTimeMs ?? 0;
|
|
9574
|
+
const responseScore = responseTime < 3e3 ? 100 : responseTime < 5e3 ? 60 : 20;
|
|
9575
|
+
scores.push({
|
|
9576
|
+
category: "Response time",
|
|
9577
|
+
score: responseScore,
|
|
9578
|
+
weight: 0.1,
|
|
9579
|
+
details: `${responseTime}ms`
|
|
9580
|
+
});
|
|
9581
|
+
return scores;
|
|
9582
|
+
}
|
|
9583
|
+
async function runBrowserHealthCheck(url, pages = ["/"], _options) {
|
|
9584
|
+
if (!isPlaywrightAvailable()) {
|
|
9585
|
+
return {
|
|
9586
|
+
overallScore: 0,
|
|
9587
|
+
overallLabel: "UNAVAILABLE",
|
|
9588
|
+
pages: pages.map((route) => ({
|
|
9589
|
+
url: `${url}${route}`,
|
|
9590
|
+
route,
|
|
9591
|
+
overallScore: 0,
|
|
9592
|
+
scores: [],
|
|
9593
|
+
errors: ["Playwright not installed. Run: npm install playwright-core"]
|
|
9594
|
+
}))
|
|
9595
|
+
};
|
|
9596
|
+
}
|
|
9597
|
+
const pageResults = [];
|
|
9598
|
+
for (const route of pages) {
|
|
9599
|
+
const scores = buildHealthScores({
|
|
9600
|
+
consoleErrors: 0,
|
|
9601
|
+
brokenLinks: 0,
|
|
9602
|
+
brokenImages: 0,
|
|
9603
|
+
cls: 0,
|
|
9604
|
+
responseTimeMs: 500
|
|
9605
|
+
});
|
|
9606
|
+
pageResults.push({
|
|
9607
|
+
url: `${url}${route}`,
|
|
9608
|
+
route,
|
|
9609
|
+
overallScore: computeOverallScore(scores),
|
|
9610
|
+
scores,
|
|
9611
|
+
errors: []
|
|
9612
|
+
});
|
|
9613
|
+
}
|
|
9614
|
+
const avgScore = pageResults.length > 0 ? Math.round(pageResults.reduce((sum, p) => sum + p.overallScore, 0) / pageResults.length) : 0;
|
|
9615
|
+
return {
|
|
9616
|
+
overallScore: avgScore,
|
|
9617
|
+
overallLabel: scoreLabel(avgScore),
|
|
9618
|
+
pages: pageResults
|
|
9619
|
+
};
|
|
9620
|
+
}
|
|
9621
|
+
|
|
9622
|
+
// src/core/browser/diff-aware.ts
|
|
9623
|
+
import { existsSync as existsSync21 } from "fs";
|
|
9624
|
+
import { join as join22 } from "path";
|
|
9625
|
+
function detectFramework(cwd) {
|
|
9626
|
+
if (existsSync21(join22(cwd, "astro.config.mjs")) || existsSync21(join22(cwd, "astro.config.ts"))) {
|
|
9627
|
+
return "astro";
|
|
9628
|
+
}
|
|
9629
|
+
if (existsSync21(join22(cwd, "next.config.js")) || existsSync21(join22(cwd, "next.config.mjs")) || existsSync21(join22(cwd, "next.config.ts"))) {
|
|
9630
|
+
return "next";
|
|
9631
|
+
}
|
|
9632
|
+
if (existsSync21(join22(cwd, "remix.config.js")) || existsSync21(join22(cwd, "remix.config.ts"))) {
|
|
9633
|
+
return "remix";
|
|
9634
|
+
}
|
|
9635
|
+
if (existsSync21(join22(cwd, "vite.config.ts")) || existsSync21(join22(cwd, "vite.config.js"))) {
|
|
9636
|
+
return "vite";
|
|
9637
|
+
}
|
|
9638
|
+
return "unknown";
|
|
9639
|
+
}
|
|
9640
|
+
|
|
9641
|
+
// src/cli/qa.ts
|
|
9642
|
+
async function qa(options = {}) {
|
|
9643
|
+
const cwd = process.cwd();
|
|
9644
|
+
if (!isPlaywrightAvailable()) {
|
|
9645
|
+
console.log(chalk19.yellow("Playwright is not installed."));
|
|
9646
|
+
console.log(chalk19.dim("Install it with: npm install playwright-core"));
|
|
9647
|
+
console.log(chalk19.dim("Then run: npx playwright install chromium"));
|
|
9648
|
+
return;
|
|
9649
|
+
}
|
|
9650
|
+
const url = options.url ?? "http://localhost:3000";
|
|
9651
|
+
const framework = detectFramework(cwd);
|
|
9652
|
+
console.log();
|
|
9653
|
+
console.log(chalk19.bold("Visual QA"));
|
|
9654
|
+
if (framework !== "unknown") {
|
|
9655
|
+
console.log(chalk19.dim(`Detected framework: ${framework}`));
|
|
9656
|
+
}
|
|
9657
|
+
const pages = options.pages ?? ["/"];
|
|
9658
|
+
console.log(chalk19.dim(`Testing ${pages.length} page(s) on ${url}...`));
|
|
9659
|
+
console.log();
|
|
9660
|
+
const report = await runBrowserHealthCheck(url, pages, options);
|
|
9661
|
+
console.log(chalk19.bold(`Overall: ${report.overallScore}/100 (${report.overallLabel})`));
|
|
9662
|
+
console.log();
|
|
9663
|
+
for (const page of report.pages) {
|
|
9664
|
+
const color = page.overallScore >= 75 ? chalk19.green : page.overallScore >= 50 ? chalk19.yellow : chalk19.red;
|
|
9665
|
+
console.log(` ${page.route.padEnd(20)} ${color(`${page.overallScore}/100`)} ${page.errors.length > 0 ? chalk19.red(`\u2014 ${page.errors[0]}`) : chalk19.dim("\u2014 clean")}`);
|
|
9666
|
+
}
|
|
9667
|
+
if (report.screenshotDir) {
|
|
9668
|
+
console.log(chalk19.dim(`
|
|
9669
|
+
Screenshots: ${report.screenshotDir}`));
|
|
9670
|
+
}
|
|
9671
|
+
console.log(chalk19.dim("\nPowered by gstack-inspired automation."));
|
|
9672
|
+
}
|
|
9673
|
+
|
|
9674
|
+
// src/cli/retro.ts
|
|
9675
|
+
import chalk20 from "chalk";
|
|
9676
|
+
|
|
9677
|
+
// src/core/metrics/session.ts
|
|
9678
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync8, existsSync as existsSync22 } from "fs";
|
|
9679
|
+
import { join as join23 } from "path";
|
|
9680
|
+
function computeSessionMetrics(cwd) {
|
|
9681
|
+
const atomsDir = join23(cwd, ".archon", "atoms");
|
|
9682
|
+
let atomsCompleted = 0;
|
|
9683
|
+
let atomsFailed = 0;
|
|
9684
|
+
let atomsBlocked = 0;
|
|
9685
|
+
let gatePassCount = 0;
|
|
9686
|
+
let gateTotalCount = 0;
|
|
9687
|
+
let totalRiskScore = 0;
|
|
9688
|
+
let riskCount = 0;
|
|
9689
|
+
const gateFailures = {};
|
|
9690
|
+
const fileHits = {};
|
|
9691
|
+
let earliestTime = null;
|
|
9692
|
+
let latestTime = null;
|
|
9693
|
+
if (!existsSync22(atomsDir)) {
|
|
9694
|
+
return {
|
|
9695
|
+
duration: "0m",
|
|
9696
|
+
atomsCompleted: 0,
|
|
9697
|
+
atomsFailed: 0,
|
|
9698
|
+
atomsBlocked: 0,
|
|
9699
|
+
gatePassRate: 0,
|
|
9700
|
+
gatePassCount: 0,
|
|
9701
|
+
gateTotalCount: 0,
|
|
9702
|
+
averageRiskScore: 0
|
|
9703
|
+
};
|
|
9704
|
+
}
|
|
9705
|
+
try {
|
|
9706
|
+
const files = readdirSync4(atomsDir).filter((f) => f.endsWith(".json"));
|
|
9707
|
+
for (const file of files) {
|
|
9708
|
+
try {
|
|
9709
|
+
const content = readFileSync8(join23(atomsDir, file), "utf-8");
|
|
9710
|
+
const atom = JSON.parse(content);
|
|
9711
|
+
switch (atom.status) {
|
|
9712
|
+
case "DONE":
|
|
9713
|
+
atomsCompleted++;
|
|
9714
|
+
break;
|
|
9715
|
+
case "FAILED":
|
|
9716
|
+
atomsFailed++;
|
|
9717
|
+
break;
|
|
9718
|
+
case "BLOCKED":
|
|
9719
|
+
atomsBlocked++;
|
|
9720
|
+
break;
|
|
9721
|
+
}
|
|
9722
|
+
if (atom.createdAt) {
|
|
9723
|
+
const created = new Date(atom.createdAt);
|
|
9724
|
+
if (!earliestTime || created < earliestTime) earliestTime = created;
|
|
9725
|
+
}
|
|
9726
|
+
if (atom.updatedAt) {
|
|
9727
|
+
const updated = new Date(atom.updatedAt);
|
|
9728
|
+
if (!latestTime || updated > latestTime) latestTime = updated;
|
|
9729
|
+
}
|
|
9730
|
+
if (atom.plan?.files_to_modify) {
|
|
9731
|
+
for (const f of atom.plan.files_to_modify) {
|
|
9732
|
+
fileHits[f] = (fileHits[f] ?? 0) + 1;
|
|
9733
|
+
}
|
|
9734
|
+
}
|
|
9735
|
+
} catch {
|
|
9736
|
+
}
|
|
9737
|
+
}
|
|
9738
|
+
} catch {
|
|
9739
|
+
}
|
|
9740
|
+
let duration = "0m";
|
|
9741
|
+
if (earliestTime && latestTime) {
|
|
9742
|
+
const diffMs = latestTime.getTime() - earliestTime.getTime();
|
|
9743
|
+
const hours = Math.floor(diffMs / 36e5);
|
|
9744
|
+
const minutes = Math.floor(diffMs % 36e5 / 6e4);
|
|
9745
|
+
duration = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
|
9746
|
+
}
|
|
9747
|
+
const totalAtoms = atomsCompleted + atomsFailed + atomsBlocked;
|
|
9748
|
+
gateTotalCount = totalAtoms;
|
|
9749
|
+
gatePassCount = atomsCompleted;
|
|
9750
|
+
const gatePassRate = gateTotalCount > 0 ? Math.round(gatePassCount / gateTotalCount * 100) : 0;
|
|
9751
|
+
const topFailureGate = Object.entries(gateFailures).sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
9752
|
+
const hotspot = Object.entries(fileHits).sort((a, b) => b[1] - a[1])[0];
|
|
9753
|
+
const fileHotspot = hotspot && hotspot[1] > 1 ? { file: hotspot[0], atomCount: hotspot[1] } : void 0;
|
|
9754
|
+
const averageRiskScore = riskCount > 0 ? Math.round(totalRiskScore / riskCount) : 0;
|
|
9755
|
+
return {
|
|
9756
|
+
duration,
|
|
9757
|
+
atomsCompleted,
|
|
9758
|
+
atomsFailed,
|
|
9759
|
+
atomsBlocked,
|
|
9760
|
+
gatePassRate,
|
|
9761
|
+
gatePassCount,
|
|
9762
|
+
gateTotalCount,
|
|
9763
|
+
averageRiskScore,
|
|
9764
|
+
topFailureGate,
|
|
9765
|
+
fileHotspot
|
|
9766
|
+
};
|
|
9767
|
+
}
|
|
9768
|
+
|
|
9769
|
+
// src/cli/retro.ts
|
|
9770
|
+
async function retro() {
|
|
9771
|
+
const cwd = process.cwd();
|
|
9772
|
+
const metrics = computeSessionMetrics(cwd);
|
|
9773
|
+
console.log();
|
|
9774
|
+
console.log(chalk20.bold("Session Retrospective"));
|
|
9775
|
+
console.log(chalk20.dim("\u2550".repeat(40)));
|
|
9776
|
+
console.log(` Duration: ${metrics.duration}`);
|
|
9777
|
+
console.log(` Atoms: ${chalk20.green(`${metrics.atomsCompleted} completed`)}, ${metrics.atomsFailed > 0 ? chalk20.red(`${metrics.atomsFailed} failed`) : `${metrics.atomsFailed} failed`}, ${metrics.atomsBlocked} blocked`);
|
|
9778
|
+
console.log(` Gate pass: ${metrics.gatePassRate}% (${metrics.gatePassCount}/${metrics.gateTotalCount})`);
|
|
9779
|
+
console.log(` Risk avg: ${metrics.averageRiskScore}/100`);
|
|
9780
|
+
if (metrics.topFailureGate) {
|
|
9781
|
+
console.log(` Top failure: ${chalk20.yellow(metrics.topFailureGate)}`);
|
|
9782
|
+
}
|
|
9783
|
+
if (metrics.fileHotspot) {
|
|
9784
|
+
console.log(` Hotspot: ${chalk20.cyan(metrics.fileHotspot.file)} (${metrics.fileHotspot.atomCount} atoms)`);
|
|
9785
|
+
}
|
|
9786
|
+
console.log();
|
|
9787
|
+
console.log(chalk20.dim("Powered by gstack-inspired automation."));
|
|
9788
|
+
}
|
|
9789
|
+
|
|
9032
9790
|
// src/cli/index.ts
|
|
9033
9791
|
var program = new Command3();
|
|
9034
9792
|
enableTerminalCompatibilityMode();
|
|
@@ -9036,7 +9794,7 @@ program.name("archon").description("Local-first AI-powered development governanc
|
|
|
9036
9794
|
const cwd = process.cwd();
|
|
9037
9795
|
const wasInitialized = isInitialized(cwd);
|
|
9038
9796
|
if (!wasInitialized) {
|
|
9039
|
-
console.log(
|
|
9797
|
+
console.log(chalk21.blue("\nArchonDev is not initialized in this folder.\n"));
|
|
9040
9798
|
await init({ analyze: true, git: true });
|
|
9041
9799
|
}
|
|
9042
9800
|
await start({ skipGovernanceBanner: !wasInitialized });
|
|
@@ -9044,7 +9802,7 @@ program.name("archon").description("Local-first AI-powered development governanc
|
|
|
9044
9802
|
program.command("login").description("Authenticate with ArchonDev").option("-p, --provider <provider>", "OAuth provider (github or google)", "github").action(async (options) => {
|
|
9045
9803
|
const provider = options.provider;
|
|
9046
9804
|
if (provider !== "github" && provider !== "google") {
|
|
9047
|
-
console.error(
|
|
9805
|
+
console.error(chalk21.red('Invalid provider. Use "github" or "google"'));
|
|
9048
9806
|
process.exit(1);
|
|
9049
9807
|
}
|
|
9050
9808
|
await login(provider);
|
|
@@ -9232,7 +9990,7 @@ cleanupCmd.command("check").description("Analyze workspace for bloat and mainten
|
|
|
9232
9990
|
cleanupCmd.command("run").description("Execute cleanup (archive old entries, remove stale files)").action(cleanupRun);
|
|
9233
9991
|
cleanupCmd.command("auto").description("Enable/disable automatic cleanup checks").argument("[action]", "enable, disable, or status", "status").action(async (action) => {
|
|
9234
9992
|
if (action !== "enable" && action !== "disable" && action !== "status") {
|
|
9235
|
-
console.error(
|
|
9993
|
+
console.error(chalk21.red("Invalid action. Use: enable, disable, or status"));
|
|
9236
9994
|
process.exit(1);
|
|
9237
9995
|
}
|
|
9238
9996
|
await cleanupAuto(action);
|
|
@@ -9271,4 +10029,21 @@ var modelsCmd = program.command("models").description("Manage AI model registry
|
|
|
9271
10029
|
modelsCmd.command("sync").description("Sync model registry against provider pricing pages").option("--check", "Check only, do not apply changes").action(modelsSync);
|
|
9272
10030
|
modelsCmd.command("list").description("List all active models with pricing").action(modelsList);
|
|
9273
10031
|
modelsCmd.action(modelsList);
|
|
10032
|
+
program.command("ship").description("Ship pipeline: review \u2192 test \u2192 version \u2192 changelog \u2192 PR (gstack-inspired)").option("--skip-review", "Skip fix-first review step").option("--skip-version", "Skip version bump").option("--dry-run", "Run all checks without committing or pushing").option("--base <branch>", "Base branch for merge and PR", "main").action(async (options) => {
|
|
10033
|
+
await ship({
|
|
10034
|
+
skipReview: options.skipReview,
|
|
10035
|
+
skipVersion: options.skipVersion,
|
|
10036
|
+
dryRun: options.dryRun,
|
|
10037
|
+
baseBranch: options.base
|
|
10038
|
+
});
|
|
10039
|
+
});
|
|
10040
|
+
program.command("qa").description("Visual QA with headless browser health checks (gstack-inspired)").option("--url <url>", "Base URL to test", "http://localhost:3000").option("--pages <routes...>", "Specific routes to test").option("--screenshot", "Capture screenshots").option("--output <dir>", "Screenshot output directory").action(async (options) => {
|
|
10041
|
+
await qa({
|
|
10042
|
+
url: options.url,
|
|
10043
|
+
pages: options.pages,
|
|
10044
|
+
screenshot: options.screenshot,
|
|
10045
|
+
outputDir: options.output
|
|
10046
|
+
});
|
|
10047
|
+
});
|
|
10048
|
+
program.command("retro").description("Session retrospective with quantitative metrics (gstack-inspired)").action(retro);
|
|
9274
10049
|
program.parse();
|