auriga-cli 1.26.0 → 1.28.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/README.md +7 -5
- package/README.zh-CN.md +7 -5
- package/dist/catalog.json +14 -4
- package/dist/state.js +15 -16
- package/dist/workflow-markers.d.ts +59 -0
- package/dist/workflow-markers.js +116 -0
- package/dist/workflow.js +117 -36
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ This repo itself is a fully configured harness project. You can clone it to see
|
|
|
13
13
|
| **Workflow** | `CLAUDE.md` auriga workflow: requirement clarification -> TDD -> Review, Harness principles, Subagent usage guide |
|
|
14
14
|
| **Skills** | External development process skills — systematic-debugging, TDD, verification, planning, playwright (spec authoring and architecture design ship as the `spec-design` and `arch-design` skills inside the `auriga-workflow` plugin) |
|
|
15
15
|
| **Recommended Skills** | Optional utility skills (e.g. `codex-agent`, `claude-code-agent`) you can add on top of the workflow skills |
|
|
16
|
-
| **Plugins** | Recommended Claude Code and Codex plugins — skill-creator, claude-md-management, codex, auriga-workflow, auriga-notify, session-instructions-loader |
|
|
16
|
+
| **Plugins** | Recommended Claude Code and Codex plugins — skill-creator, claude-md-management, playground, codex, auriga-workflow, auriga-notify, session-instructions-loader |
|
|
17
17
|
|
|
18
18
|
## Quick Start
|
|
19
19
|
|
|
@@ -87,9 +87,10 @@ The **Recommended preset** is checked by default and installs silently with the
|
|
|
87
87
|
|
|
88
88
|
### Workflow
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Installs `CLAUDE.md` into the target project and creates an `AGENTS.md` symlink for compatibility with different Agent frameworks. Supports English and Chinese — you choose during installation.
|
|
91
91
|
|
|
92
|
-
-
|
|
92
|
+
- **Extensible and upgradable**: the auriga workflow ships inside a managed block delimited by `<!-- AURIGA:WORKFLOW:v1 START/END -->` markers. Add your project-specific instructions *after* the END marker — re-running install upgrades the managed block in place and leaves your section untouched.
|
|
93
|
+
- A pre-marker `CLAUDE.md` (installed by an older version) is migrated to the marked format on the next install, with the old file backed up to `CLAUDE.md.bak`. A foreign `CLAUDE.md` from another tool is kept as your user section below a fresh managed block.
|
|
93
94
|
- Covers: requirement clarification, TDD, code review, branch workflow, subagent orchestration
|
|
94
95
|
|
|
95
96
|
### Skills
|
|
@@ -133,9 +134,10 @@ npx -y auriga-cli install plugins --agent codex --plugin session-instructions-lo
|
|
|
133
134
|
| Plugin | Runtime | Description |
|
|
134
135
|
|---|---|---|
|
|
135
136
|
| skill-creator | Claude Code | Create and manage custom skills |
|
|
136
|
-
| claude-md-management | Claude Code | Audit and improve CLAUDE.md |
|
|
137
|
+
| claude-md-management | Claude Code / Codex | Audit and improve CLAUDE.md |
|
|
138
|
+
| playground | Claude Code / Codex | Build interactive HTML playgrounds |
|
|
137
139
|
| codex | Claude Code | Codex cross-model collaboration |
|
|
138
|
-
| auriga-workflow | Claude Code / Codex | The auriga workflow plugin — workflow skills plus the git lifecycle hooks that enforce them. Skills: `incremental-impl`, `test-designer`, `spec-design`, `arch-design`, `code-simplify`, `session-compound`, `goalify` (plans an autonomous goal and dispatches it via the built-in `/goal` command), `deep-review` (multi-dimensional PR review orchestrator — parallel per-dimension reviewers synthesized into an actionable punch list), `reviewer-creator` (scaffolds project-level custom reviewers under `docs/rules/review/`), and `git-workflow` (git lifecycle skill). Hooks: `commit-reminder` (PostToolUse on file edits — `Edit` / `Write` / `MultiEdit` in Claude Code, `apply_patch` in Codex — nudges to commit at the next semantic boundary when uncommitted diff vs `HEAD` exceeds 200 lines or 8 files), `pr-create-guard` (PostToolUse on `gh pr create` → injects a PR-body snapshot for five-element self-verification and flags non-Conventional-Commits titles),
|
|
140
|
+
| auriga-workflow | Claude Code / Codex | The auriga workflow plugin — workflow skills plus the git lifecycle hooks that enforce them. Skills: `incremental-impl`, `test-designer`, `spec-design`, `arch-design`, `code-simplify`, `session-compound`, `goalify` (plans an autonomous goal and dispatches it via the built-in `/goal` command), `deep-review` (multi-dimensional PR review orchestrator — parallel per-dimension reviewers synthesized into an actionable punch list), `reviewer-creator` (scaffolds project-level custom reviewers under `docs/rules/review/`), and `git-workflow` (git lifecycle skill). Hooks: `commit-reminder` (PostToolUse on file edits — `Edit` / `Write` / `MultiEdit` in Claude Code, `apply_patch` in Codex — nudges to commit at the next semantic boundary when uncommitted diff vs `HEAD` exceeds 200 lines or 8 files), `pr-create-guard` (PostToolUse on `gh pr create` → injects a PR-body snapshot for five-element self-verification and flags non-Conventional-Commits titles), `pr-ready-guard` (PreToolUse on `gh pr ready` and non-draft `gh pr create` → blocks on stray planning docs, unfinalized active specs under `docs/specs/`, or unpushed commits), and `pr-merge-guard` (PreToolUse on `gh pr merge` → blocks while the PR body's Acceptance criteria section still has unchecked checklist items). The two PostToolUse hooks reach full Claude Code / Codex parity; Codex currently fails open on `pr-ready-guard`'s PreToolUse `additionalContext` informational path (block path identical). Installed by default through the plugin path. |
|
|
139
141
|
| auriga-notify *(opt-in)* | Claude Code | macOS native notification plugin for Claude Code `Notification` events. Focus-aware sound-only mode, click-to-activate, per-project notification grouping, and migrated `config.json` / `icon.png` support. Not installed by `install --all`; install explicitly with `install plugins --plugin auriga-notify`. |
|
|
140
142
|
| session-instructions-loader | Codex | Codex-only SessionStart plugin that injects ancestor `AGENTS.md` files plus repo-configured extra instruction files. |
|
|
141
143
|
|
package/README.zh-CN.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
| **Workflow** | `CLAUDE.md` 里的 auriga 工作流:需求澄清 → TDD → Review,Harness 原则,Subagent 使用指南 |
|
|
14
14
|
| **Skills** | 外部开发流程 skills —— systematic-debugging、TDD、verification、planning、playwright(spec 撰写与架构设计由 `auriga-workflow` 插件内的 `spec-design`、`arch-design` skill 提供)|
|
|
15
15
|
| **Recommended Skills** | 可选的工具类 skills(如 `codex-agent`、`claude-code-agent`),在 workflow skills 之外按需追加 |
|
|
16
|
-
| **Plugins** | 推荐的 Claude Code 和 Codex 插件 —— skill-creator、claude-md-management、codex、auriga-workflow、auriga-notify、session-instructions-loader |
|
|
16
|
+
| **Plugins** | 推荐的 Claude Code 和 Codex 插件 —— skill-creator、claude-md-management、playground、codex、auriga-workflow、auriga-notify、session-instructions-loader |
|
|
17
17
|
|
|
18
18
|
## 快速开始
|
|
19
19
|
|
|
@@ -87,9 +87,10 @@ npx auriga-cli
|
|
|
87
87
|
|
|
88
88
|
### Workflow
|
|
89
89
|
|
|
90
|
-
将 `CLAUDE.md`
|
|
90
|
+
将 `CLAUDE.md` 安装到目标项目,并创建 `AGENTS.md` 软链接以兼容不同 Agent 框架。支持中英文版本,安装时可选择。
|
|
91
91
|
|
|
92
|
-
-
|
|
92
|
+
- **可扩展、可升级**:auriga 工作流被一对 `<!-- AURIGA:WORKFLOW:v1 START/END -->` 标记包成「受管区块」。把你的工程专属规则写在 END 标记**之后**——再次安装只就地升级受管区块,你的内容原样保留。
|
|
93
|
+
- 旧版本装下的、无标记的 `CLAUDE.md` 会在下次安装时迁移为标记格式,旧文件备份到 `CLAUDE.md.bak`。别的工具生成的 `CLAUDE.md` 会作为用户区保留在全新受管区块下方。
|
|
93
94
|
- 涵盖:需求澄清、TDD、代码 Review、分支工作流、Subagent 编排
|
|
94
95
|
|
|
95
96
|
### Skills
|
|
@@ -133,9 +134,10 @@ npx -y auriga-cli install plugins --agent codex --plugin session-instructions-lo
|
|
|
133
134
|
| 插件 | 运行时 | 说明 |
|
|
134
135
|
|---|---|---|
|
|
135
136
|
| skill-creator | Claude Code | 创建和管理自定义 skills |
|
|
136
|
-
| claude-md-management | Claude Code | 审计和改进 CLAUDE.md |
|
|
137
|
+
| claude-md-management | Claude Code / Codex | 审计和改进 CLAUDE.md |
|
|
138
|
+
| playground | Claude Code / Codex | 构建交互式 HTML playground |
|
|
137
139
|
| codex | Claude Code | Codex 跨模型协作 |
|
|
138
|
-
| auriga-workflow | Claude Code / Codex | auriga 工作流插件 —— 工作流 skill 加上强制执行工作流的 git 生命周期 hook。Skills:`incremental-impl`、`test-designer`、`spec-design`、`arch-design`、`code-simplify`、`session-compound`、`goalify`(plan 出自驱 goal 并通过内置 `/goal` 命令分发执行)、`deep-review`(多维度 PR review 编排器——并行派发各维度 reviewer,汇总成可执行的 punch list)、`reviewer-creator`(在 `docs/rules/review/` 下生成项目级自定义 reviewer)、`git-workflow`(git 生命周期 skill)。Hooks:`commit-reminder`(文件编辑的 PostToolUse —— Claude Code 匹配 `Edit` / `Write` / `MultiEdit`,Codex 匹配 `apply_patch` —— 未提交 diff 对比 `HEAD` 超过 200 行或 8 个文件时,提醒在下一个语义边界 commit)、`pr-create-guard`(`gh pr create` 的 PostToolUse —— 注入 PR body 快照供五要素自检,并对不符合 Conventional Commits 的标题提示)、`pr-ready-guard`(`gh pr ready` 与非 draft `gh pr create` 的 PreToolUse —— 拦截游离规划文档、`docs/specs/` 内未结案的活跃 spec、未 push commits
|
|
140
|
+
| auriga-workflow | Claude Code / Codex | auriga 工作流插件 —— 工作流 skill 加上强制执行工作流的 git 生命周期 hook。Skills:`incremental-impl`、`test-designer`、`spec-design`、`arch-design`、`code-simplify`、`session-compound`、`goalify`(plan 出自驱 goal 并通过内置 `/goal` 命令分发执行)、`deep-review`(多维度 PR review 编排器——并行派发各维度 reviewer,汇总成可执行的 punch list)、`reviewer-creator`(在 `docs/rules/review/` 下生成项目级自定义 reviewer)、`git-workflow`(git 生命周期 skill)。Hooks:`commit-reminder`(文件编辑的 PostToolUse —— Claude Code 匹配 `Edit` / `Write` / `MultiEdit`,Codex 匹配 `apply_patch` —— 未提交 diff 对比 `HEAD` 超过 200 行或 8 个文件时,提醒在下一个语义边界 commit)、`pr-create-guard`(`gh pr create` 的 PostToolUse —— 注入 PR body 快照供五要素自检,并对不符合 Conventional Commits 的标题提示)、`pr-ready-guard`(`gh pr ready` 与非 draft `gh pr create` 的 PreToolUse —— 拦截游离规划文档、`docs/specs/` 内未结案的活跃 spec、未 push commits)、`pr-merge-guard`(`gh pr merge` 的 PreToolUse —— PR body 的验收标准章节仍有未勾选清单项时拦截合并)。两个 PostToolUse hook 在 Claude Code / Codex 上完全对齐;Codex 仅对 `pr-ready-guard` 的 PreToolUse `additionalContext` 信息路径 fail-open(block 路径两边一致)。默认通过插件路径安装。 |
|
|
139
141
|
| auriga-notify *(opt-in)* | Claude Code | Claude Code `Notification` 事件的 macOS 原生通知插件。支持焦点感知仅提示音、点击唤起终端、按项目分组通知,并迁移旧 `config.json` / `icon.png`。不随 `install --all` 默认安装,需要显式执行 `install plugins --plugin auriga-notify`。 |
|
|
140
142
|
| session-instructions-loader | Codex | Codex-only SessionStart 插件,注入上层目录的 `AGENTS.md` 和仓库配置的额外 instruction 文件。 |
|
|
141
143
|
|
package/dist/catalog.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-05-
|
|
2
|
+
"generatedAt": "2026-05-16T10:40:59.234Z",
|
|
3
3
|
"workflowSkills": [
|
|
4
4
|
{
|
|
5
5
|
"name": "planning-with-files",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"plugins": [
|
|
56
56
|
{
|
|
57
57
|
"name": "auriga-workflow",
|
|
58
|
-
"description": "(Claude/Codex) The auriga workflow plugin: workflow skills — incremental-impl (implementation slicing), test-designer (independent test design), spec-design (requirement clarification), arch-design (architecture design), code-simplify (code simplification), session-compound (session compounding), goalify (autonomous /goal planning), deep-review (multi-dimensional PR review orchestrator), reviewer-creator (custom reviewer scaffolding), git-workflow (git lifecycle skill) — plus the git lifecycle hooks that enforce them: commit-reminder, pr-create-guard, pr-ready-guard. Dual-Agent compatible (Claude Code + Codex).",
|
|
58
|
+
"description": "(Claude/Codex) The auriga workflow plugin: workflow skills — incremental-impl (implementation slicing), test-designer (independent test design), spec-design (requirement clarification), arch-design (architecture design), code-simplify (code simplification), session-compound (session compounding), goalify (autonomous /goal planning), deep-review (multi-dimensional PR review orchestrator), reviewer-creator (custom reviewer scaffolding), git-workflow (git lifecycle skill) — plus the git lifecycle hooks that enforce them: commit-reminder, pr-create-guard, pr-ready-guard, pr-merge-guard. Dual-Agent compatible (Claude Code + Codex).",
|
|
59
59
|
"agents": [
|
|
60
60
|
"claude",
|
|
61
61
|
"codex"
|
|
@@ -78,9 +78,19 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"name": "claude-md-management",
|
|
81
|
-
"description": "Audit and improve CLAUDE.md files",
|
|
81
|
+
"description": "(Claude/Codex) Audit and improve CLAUDE.md files",
|
|
82
82
|
"agents": [
|
|
83
|
-
"claude"
|
|
83
|
+
"claude",
|
|
84
|
+
"codex"
|
|
85
|
+
],
|
|
86
|
+
"external": true
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "playground",
|
|
90
|
+
"description": "(Claude/Codex) Build interactive HTML playgrounds",
|
|
91
|
+
"agents": [
|
|
92
|
+
"claude",
|
|
93
|
+
"codex"
|
|
84
94
|
],
|
|
85
95
|
"external": true
|
|
86
96
|
},
|
package/dist/state.js
CHANGED
|
@@ -24,6 +24,7 @@ import fs from "node:fs";
|
|
|
24
24
|
import os from "node:os";
|
|
25
25
|
import path from "node:path";
|
|
26
26
|
import { parse as parseToml } from "smol-toml";
|
|
27
|
+
import { hasAurigaHeader, parseMarkers } from "./workflow-markers.js";
|
|
27
28
|
/**
|
|
28
29
|
* Shorten an absolute path by replacing the user's $HOME with `~`. Avoids
|
|
29
30
|
* leaking the full username in screenshots and keeps the TopBar label
|
|
@@ -147,7 +148,6 @@ function aggregateStatus(records) {
|
|
|
147
148
|
// ---------------------------------------------------------------------------
|
|
148
149
|
// Workflow
|
|
149
150
|
// ---------------------------------------------------------------------------
|
|
150
|
-
const WORKFLOW_HEADER_RE = /^#\s+auriga\s+Workflow\s*\(v\d+\.\d+\.\d+\)/;
|
|
151
151
|
function workflowPathsForScope(scope, projectRoot, home) {
|
|
152
152
|
if (scope === "user") {
|
|
153
153
|
return [path.join(home, ".claude", "CLAUDE.md")];
|
|
@@ -174,23 +174,22 @@ function scanWorkflow(scope, projectRoot, home, warnings) {
|
|
|
174
174
|
if (content === null) {
|
|
175
175
|
return { status: "not-installed", observedScope: scope };
|
|
176
176
|
}
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
// `CLAUDE.md.bak` (backup-once: never clobbers a prior .bak).
|
|
177
|
+
// "Is this our CLAUDE.md?" — two recognizable shapes:
|
|
178
|
+
// - managed-block markers (the current install format). The START marker
|
|
179
|
+
// is an HTML comment ahead of the auriga header, so a first-non-blank-
|
|
180
|
+
// line header walk would miss it — detect the marker pair directly.
|
|
181
|
+
// - an auriga workflow header with no markers (a pre-marker install).
|
|
182
|
+
// Still ours; the next install migrates it to the marked format.
|
|
183
|
+
if (parseMarkers(content).kind === "marked" || hasAurigaHeader(content)) {
|
|
184
|
+
return { status: "installed", observedScope: scope };
|
|
185
|
+
}
|
|
186
|
+
// CLAUDE.md exists but is neither marked nor auriga-headed. The file is
|
|
187
|
+
// foreign — not our workflow. Report `not-installed` honestly; the install
|
|
188
|
+
// path (src/workflow.ts) keeps the foreign content as the user region
|
|
189
|
+
// below a fresh managed block, so nothing is lost.
|
|
191
190
|
warnings.push({
|
|
192
191
|
code: "workflow-foreign-claudemd",
|
|
193
|
-
message: `Foreign CLAUDE.md detected at the workflow path — no auriga-workflow header. Install will
|
|
192
|
+
message: `Foreign CLAUDE.md detected at the workflow path — no auriga-workflow header. Install will keep your content as the user region below the managed block.`,
|
|
194
193
|
});
|
|
195
194
|
return { status: "not-installed", observedScope: scope };
|
|
196
195
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** Marker schema version. Frozen contract — bump only with a migration plan. */
|
|
2
|
+
export declare const MARKER_SCHEMA = "v1";
|
|
3
|
+
/** The START marker line for the given template language. Unknown languages
|
|
4
|
+
* fall back to English. */
|
|
5
|
+
export declare function workflowStartMarker(lang?: string): string;
|
|
6
|
+
/** Build the END marker line, embedding the managed-block content hash so a
|
|
7
|
+
* later upgrade can tell an untouched block from a hand-edited one. */
|
|
8
|
+
export declare function workflowEndMarker(hash: string): string;
|
|
9
|
+
/** Matches the auriga workflow H1 header in either language
|
|
10
|
+
* (`# auriga Workflow (vX.Y.Z)` / `# auriga 工作流 (vX.Y.Z)`). */
|
|
11
|
+
export declare const WORKFLOW_HEADER_RE: RegExp;
|
|
12
|
+
/** sha256 of the managed-block body, truncated to 16 hex chars. Not a security
|
|
13
|
+
* primitive — just a tamper check to distinguish untouched from hand-edited. */
|
|
14
|
+
export declare function hashBlock(blockBody: string): string;
|
|
15
|
+
export type MarkerParse = {
|
|
16
|
+
kind: "unmarked";
|
|
17
|
+
} | {
|
|
18
|
+
kind: "malformed";
|
|
19
|
+
reason: string;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "marked";
|
|
22
|
+
/** Bytes before the START marker line (normally empty). */
|
|
23
|
+
prefix: string;
|
|
24
|
+
/** Bytes strictly between the START line and the END line — the managed
|
|
25
|
+
* block. Includes the trailing newline that precedes the END line. */
|
|
26
|
+
blockBody: string;
|
|
27
|
+
/** Bytes after the END marker line — the project's own user region. */
|
|
28
|
+
userRegion: string;
|
|
29
|
+
/** sha256 recorded in the END marker, or null if the marker carried none. */
|
|
30
|
+
endHash: string | null;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Classify a CLAUDE.md body by its managed-block markers.
|
|
34
|
+
*
|
|
35
|
+
* - `unmarked` — neither marker present (fresh-target / foreign / old-format)
|
|
36
|
+
* - `malformed` — exactly one marker, or END before START (can't safely splice)
|
|
37
|
+
* - `marked` — a well-formed START…END pair
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseMarkers(content: string): MarkerParse;
|
|
40
|
+
/**
|
|
41
|
+
* Build a marked CLAUDE.md from its three parts. The END marker hash is
|
|
42
|
+
* computed from `blockBody` here, so callers never hand-maintain it.
|
|
43
|
+
*
|
|
44
|
+
* `blockBody` is expected to end with a newline (it is the content the START
|
|
45
|
+
* line's newline leads into, up to the END line). `parseMarkers` and
|
|
46
|
+
* `composeMarkedFile` are exact inverses for the block body and user region.
|
|
47
|
+
*
|
|
48
|
+
* `lang` selects the START marker's prose language (default English); it does
|
|
49
|
+
* not affect the parser, which keys on the language-independent token.
|
|
50
|
+
*/
|
|
51
|
+
export declare function composeMarkedFile(opts: {
|
|
52
|
+
prefix?: string;
|
|
53
|
+
blockBody: string;
|
|
54
|
+
userRegion?: string;
|
|
55
|
+
lang?: string;
|
|
56
|
+
}): string;
|
|
57
|
+
/** True when the first non-blank line is an auriga workflow header. Used to
|
|
58
|
+
* tell an old-format (pre-marker) auriga CLAUDE.md from a foreign one. */
|
|
59
|
+
export declare function hasAurigaHeader(content: string): boolean;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Managed-block markers for the installed CLAUDE.md.
|
|
2
|
+
//
|
|
3
|
+
// auriga-cli installs its workflow document wrapped in a pair of HTML-comment
|
|
4
|
+
// markers. Everything *between* the markers is the "managed block" — owned by
|
|
5
|
+
// auriga-cli, replaced wholesale on upgrade. Everything *outside* (notably the
|
|
6
|
+
// region after the END marker) is the project's own — auriga-cli never touches
|
|
7
|
+
// it. This lets a downstream project extend its CLAUDE.md while still
|
|
8
|
+
// receiving workflow upgrades.
|
|
9
|
+
//
|
|
10
|
+
// Markers are HTML comments so both Claude Code and Codex (which read the same
|
|
11
|
+
// file via the AGENTS.md → CLAUDE.md symlink) treat them as inert.
|
|
12
|
+
//
|
|
13
|
+
// This module is the single source of truth for the marker contract; it is
|
|
14
|
+
// imported by both src/workflow.ts (install / upgrade) and src/state.ts
|
|
15
|
+
// (presence detection). It deliberately has no heavy imports so state.ts /
|
|
16
|
+
// server.ts don't pull in @inquirer/prompts transitively.
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
/** Marker schema version. Frozen contract — bump only with a migration plan. */
|
|
19
|
+
export const MARKER_SCHEMA = "v1";
|
|
20
|
+
/**
|
|
21
|
+
* START marker line, one per template language. Only the prose differs — the
|
|
22
|
+
* structural `AURIGA:WORKFLOW:v1 START` token is language-independent, so the
|
|
23
|
+
* parser (`START_LINE_RE`) keys on the token alone and never needs to know the
|
|
24
|
+
* language. The English `CLAUDE.md` gets the English marker; `CLAUDE.zh-CN.md`
|
|
25
|
+
* gets the Chinese one, so a downstream file never carries a comment in the
|
|
26
|
+
* wrong language for its document.
|
|
27
|
+
*/
|
|
28
|
+
const WORKFLOW_START_MARKERS = {
|
|
29
|
+
en: `<!-- AURIGA:WORKFLOW:${MARKER_SCHEMA} START — Managed block, maintained by auriga-cli. Do not edit by hand; upgrades replace it wholesale. Put project-specific instructions after the END marker below. -->`,
|
|
30
|
+
"zh-CN": `<!-- AURIGA:WORKFLOW:${MARKER_SCHEMA} START — 受管区块,由 auriga-cli 维护,请勿手改;升级会整块覆盖。工程专属规则写在下方 END 标记之后。 -->`,
|
|
31
|
+
};
|
|
32
|
+
/** The START marker line for the given template language. Unknown languages
|
|
33
|
+
* fall back to English. */
|
|
34
|
+
export function workflowStartMarker(lang) {
|
|
35
|
+
return WORKFLOW_START_MARKERS[lang ?? "en"] ?? WORKFLOW_START_MARKERS.en;
|
|
36
|
+
}
|
|
37
|
+
/** Build the END marker line, embedding the managed-block content hash so a
|
|
38
|
+
* later upgrade can tell an untouched block from a hand-edited one. */
|
|
39
|
+
export function workflowEndMarker(hash) {
|
|
40
|
+
return `<!-- AURIGA:WORKFLOW:${MARKER_SCHEMA} END sha256=${hash} -->`;
|
|
41
|
+
}
|
|
42
|
+
// Marker line regexes (multiline — matched against the whole file body).
|
|
43
|
+
// START tolerates any trailing comment text after `START`; END optionally
|
|
44
|
+
// carries `sha256=<hex>`.
|
|
45
|
+
const START_LINE_RE = /^<!--\s*AURIGA:WORKFLOW:v1\s+START\b.*?-->[ \t]*$/m;
|
|
46
|
+
const END_LINE_RE = /^<!--\s*AURIGA:WORKFLOW:v1\s+END(?:\s+sha256=([0-9a-f]+))?[ \t]*-->[ \t]*$/m;
|
|
47
|
+
/** Matches the auriga workflow H1 header in either language
|
|
48
|
+
* (`# auriga Workflow (vX.Y.Z)` / `# auriga 工作流 (vX.Y.Z)`). */
|
|
49
|
+
export const WORKFLOW_HEADER_RE = /^#\s+auriga\s+(?:Workflow|工作流)\s*\(v\d+\.\d+\.\d+\)/;
|
|
50
|
+
/** sha256 of the managed-block body, truncated to 16 hex chars. Not a security
|
|
51
|
+
* primitive — just a tamper check to distinguish untouched from hand-edited. */
|
|
52
|
+
export function hashBlock(blockBody) {
|
|
53
|
+
return createHash("sha256").update(blockBody, "utf8").digest("hex").slice(0, 16);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Classify a CLAUDE.md body by its managed-block markers.
|
|
57
|
+
*
|
|
58
|
+
* - `unmarked` — neither marker present (fresh-target / foreign / old-format)
|
|
59
|
+
* - `malformed` — exactly one marker, or END before START (can't safely splice)
|
|
60
|
+
* - `marked` — a well-formed START…END pair
|
|
61
|
+
*/
|
|
62
|
+
export function parseMarkers(content) {
|
|
63
|
+
const startMatch = START_LINE_RE.exec(content);
|
|
64
|
+
const endMatch = END_LINE_RE.exec(content);
|
|
65
|
+
if (!startMatch && !endMatch)
|
|
66
|
+
return { kind: "unmarked" };
|
|
67
|
+
if (!startMatch || !endMatch) {
|
|
68
|
+
return {
|
|
69
|
+
kind: "malformed",
|
|
70
|
+
reason: startMatch ? "START marker without a matching END" : "END marker without a matching START",
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (startMatch.index >= endMatch.index) {
|
|
74
|
+
return { kind: "malformed", reason: "END marker precedes START marker" };
|
|
75
|
+
}
|
|
76
|
+
const startLineEnd = content.indexOf("\n", startMatch.index);
|
|
77
|
+
if (startLineEnd < 0 || startLineEnd > endMatch.index) {
|
|
78
|
+
return { kind: "malformed", reason: "START marker line is not terminated before END" };
|
|
79
|
+
}
|
|
80
|
+
const endLineEnd = content.indexOf("\n", endMatch.index);
|
|
81
|
+
return {
|
|
82
|
+
kind: "marked",
|
|
83
|
+
prefix: content.slice(0, startMatch.index),
|
|
84
|
+
blockBody: content.slice(startLineEnd + 1, endMatch.index),
|
|
85
|
+
userRegion: endLineEnd < 0 ? "" : content.slice(endLineEnd + 1),
|
|
86
|
+
endHash: endMatch[1] ?? null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Build a marked CLAUDE.md from its three parts. The END marker hash is
|
|
91
|
+
* computed from `blockBody` here, so callers never hand-maintain it.
|
|
92
|
+
*
|
|
93
|
+
* `blockBody` is expected to end with a newline (it is the content the START
|
|
94
|
+
* line's newline leads into, up to the END line). `parseMarkers` and
|
|
95
|
+
* `composeMarkedFile` are exact inverses for the block body and user region.
|
|
96
|
+
*
|
|
97
|
+
* `lang` selects the START marker's prose language (default English); it does
|
|
98
|
+
* not affect the parser, which keys on the language-independent token.
|
|
99
|
+
*/
|
|
100
|
+
export function composeMarkedFile(opts) {
|
|
101
|
+
return ((opts.prefix ?? "") +
|
|
102
|
+
workflowStartMarker(opts.lang) + "\n" +
|
|
103
|
+
opts.blockBody +
|
|
104
|
+
workflowEndMarker(hashBlock(opts.blockBody)) + "\n" +
|
|
105
|
+
(opts.userRegion ?? ""));
|
|
106
|
+
}
|
|
107
|
+
/** True when the first non-blank line is an auriga workflow header. Used to
|
|
108
|
+
* tell an old-format (pre-marker) auriga CLAUDE.md from a foreign one. */
|
|
109
|
+
export function hasAurigaHeader(content) {
|
|
110
|
+
for (const line of content.split("\n")) {
|
|
111
|
+
if (line.trim().length === 0)
|
|
112
|
+
continue;
|
|
113
|
+
return WORKFLOW_HEADER_RE.test(line);
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
package/dist/workflow.js
CHANGED
|
@@ -2,6 +2,36 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { input, select } from "@inquirer/prompts";
|
|
4
4
|
import { LANGUAGES, fetchExtraContent, log, withEsc, } from "./utils.js";
|
|
5
|
+
import { composeMarkedFile, hasAurigaHeader, hashBlock, parseMarkers, } from "./workflow-markers.js";
|
|
6
|
+
/**
|
|
7
|
+
* Back up `filePath` once. The canonical `<file>.bak` slot is reserved for the
|
|
8
|
+
* FIRST capture (the user's pre-auriga original) and is never overwritten — a
|
|
9
|
+
* later capture spills to a timestamped `<file>.bak.<stamp>`. Returns the path
|
|
10
|
+
* the backup was written to.
|
|
11
|
+
*
|
|
12
|
+
* `verbatimSymlinks` copies a symlink AS a symlink, preserving its literal
|
|
13
|
+
* (possibly relative) target — a foreign AGENTS.md may be a symlink pointing
|
|
14
|
+
* elsewhere, and we want the backup to preserve that target verbatim rather
|
|
15
|
+
* than snapshot whatever it currently resolves to. A real file (CLAUDE.md)
|
|
16
|
+
* copies as a real file. `lstat` (not `existsSync`) probes the `.bak` slot so
|
|
17
|
+
* a backup that is itself a possibly-broken symlink still counts as present
|
|
18
|
+
* and is not silently overwritten.
|
|
19
|
+
*/
|
|
20
|
+
function backupOnce(filePath) {
|
|
21
|
+
const bakPath = filePath + ".bak";
|
|
22
|
+
let bakExists = true;
|
|
23
|
+
try {
|
|
24
|
+
fs.lstatSync(bakPath);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
bakExists = false;
|
|
28
|
+
}
|
|
29
|
+
const dest = bakExists
|
|
30
|
+
? `${bakPath}.${new Date().toISOString().replace(/[:.]/g, "-")}`
|
|
31
|
+
: bakPath;
|
|
32
|
+
fs.cpSync(filePath, dest, { verbatimSymlinks: true });
|
|
33
|
+
return dest;
|
|
34
|
+
}
|
|
5
35
|
export async function installWorkflow(packageRoot, opts) {
|
|
6
36
|
const lang = opts.interactive
|
|
7
37
|
? await withEsc(select({
|
|
@@ -34,49 +64,100 @@ export async function installWorkflow(packageRoot, opts) {
|
|
|
34
64
|
const sourceClaude = path.join(packageRoot, langOpt.file);
|
|
35
65
|
const targetClaude = path.join(resolved, "CLAUDE.md");
|
|
36
66
|
const targetAgents = path.join(resolved, "AGENTS.md");
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
// The packaged template is authored with managed-block markers. Extract its
|
|
68
|
+
// managed block (the auriga workflow body) and its user-region placeholder.
|
|
69
|
+
// Defensive fallback: if the template somehow lacks markers, treat the whole
|
|
70
|
+
// file as the managed block with an empty user region.
|
|
71
|
+
const sourceContent = fs.readFileSync(sourceClaude, "utf8");
|
|
72
|
+
const sourceParsed = parseMarkers(sourceContent);
|
|
73
|
+
const sourceBlock = sourceParsed.kind === "marked"
|
|
74
|
+
? sourceParsed.blockBody
|
|
75
|
+
: sourceContent.endsWith("\n")
|
|
76
|
+
? sourceContent
|
|
77
|
+
: sourceContent + "\n";
|
|
78
|
+
const templateUserRegion = sourceParsed.kind === "marked" ? sourceParsed.userRegion : "";
|
|
79
|
+
// Installing the workflow doc is one of five cases. The managed block is
|
|
80
|
+
// always replaced with the packaged version; the cases differ in how the
|
|
81
|
+
// project's own content (the user region) is preserved or backed up.
|
|
82
|
+
if (!fs.existsSync(targetClaude)) {
|
|
83
|
+
// 1. Fresh install — write the marked template as-is, no backup.
|
|
84
|
+
fs.writeFileSync(targetClaude, composeMarkedFile({ blockBody: sourceBlock, userRegion: templateUserRegion, lang }));
|
|
85
|
+
log.ok(`CLAUDE.md installed (${langOpt.label})`);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
const current = fs.readFileSync(targetClaude, "utf8");
|
|
89
|
+
const parsed = parseMarkers(current);
|
|
90
|
+
if (parsed.kind === "marked") {
|
|
91
|
+
// 2. Upgrade — splice the managed block, preserve the user region.
|
|
92
|
+
// The END marker carries the block's hash. Three cases:
|
|
93
|
+
// - hash present and matches → block untouched, no backup
|
|
94
|
+
// - hash present and mismatch → block hand-edited, back up + warn
|
|
95
|
+
// - hash absent → unverifiable (e.g. the file was
|
|
96
|
+
// copied straight from the template, which ships a no-hash END
|
|
97
|
+
// marker). Can't prove the block is untouched, so back up
|
|
98
|
+
// conservatively rather than risk silently dropping an edit.
|
|
99
|
+
if (parsed.endHash === null) {
|
|
100
|
+
const bak = backupOnce(targetClaude);
|
|
101
|
+
log.warn(`CLAUDE.md 的受管区块缺少校验标记,无法确认是否被改动;升级前已备份到 ${path.basename(bak)}`);
|
|
64
102
|
}
|
|
65
|
-
else {
|
|
66
|
-
|
|
67
|
-
log.warn(`
|
|
103
|
+
else if (parsed.endHash !== hashBlock(parsed.blockBody)) {
|
|
104
|
+
const bak = backupOnce(targetClaude);
|
|
105
|
+
log.warn(`CLAUDE.md 的受管区块曾被手改;升级已整块覆盖该区块,改动前的文件见 ${path.basename(bak)}`);
|
|
68
106
|
}
|
|
107
|
+
fs.writeFileSync(targetClaude, composeMarkedFile({
|
|
108
|
+
prefix: parsed.prefix,
|
|
109
|
+
blockBody: sourceBlock,
|
|
110
|
+
userRegion: parsed.userRegion,
|
|
111
|
+
lang,
|
|
112
|
+
}));
|
|
113
|
+
log.ok(`CLAUDE.md upgraded (${langOpt.label}); your project section was preserved`);
|
|
114
|
+
}
|
|
115
|
+
else if (parsed.kind === "unmarked" && hasAurigaHeader(current)) {
|
|
116
|
+
// 3. Old-format migration — an auriga CLAUDE.md from before markers
|
|
117
|
+
// existed. The user region can't be recovered from an unmarked file,
|
|
118
|
+
// so back the whole thing up and install fresh.
|
|
119
|
+
const bak = backupOnce(targetClaude);
|
|
120
|
+
fs.writeFileSync(targetClaude, composeMarkedFile({ blockBody: sourceBlock, userRegion: templateUserRegion, lang }));
|
|
121
|
+
log.warn(`检测到旧版 CLAUDE.md(无受管标记);已备份到 ${path.basename(bak)}。` +
|
|
122
|
+
`若你改过它,请从备份把工程定制手动迁移到 END 标记之后的用户区。`);
|
|
123
|
+
log.ok(`CLAUDE.md migrated to the managed-block format (${langOpt.label})`);
|
|
124
|
+
}
|
|
125
|
+
else if (parsed.kind === "unmarked") {
|
|
126
|
+
// 4. Foreign first install — a CLAUDE.md from another tool. Keep its
|
|
127
|
+
// content in place as the user region; no backup needed.
|
|
128
|
+
const foreign = current.endsWith("\n") ? current : current + "\n";
|
|
129
|
+
fs.writeFileSync(targetClaude, composeMarkedFile({ blockBody: sourceBlock, userRegion: "\n" + foreign, lang }));
|
|
130
|
+
log.ok(`CLAUDE.md installed (${langOpt.label}); your existing content was kept below the managed block`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// 5. Malformed markers — can't locate the block boundaries safely.
|
|
134
|
+
// Back up and reinstall fresh rather than splice into a broken file.
|
|
135
|
+
const bak = backupOnce(targetClaude);
|
|
136
|
+
fs.writeFileSync(targetClaude, composeMarkedFile({ blockBody: sourceBlock, userRegion: templateUserRegion, lang }));
|
|
137
|
+
log.warn(`CLAUDE.md 的受管标记已损坏(${parsed.reason});已备份到 ${path.basename(bak)} 并重装。`);
|
|
69
138
|
}
|
|
70
139
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
140
|
+
// Point AGENTS.md at CLAUDE.md via a symlink (the install shape — Claude
|
|
141
|
+
// Code and Codex then read the same workflow doc). If the path is already
|
|
142
|
+
// occupied by something that ISN'T that symlink — a real file from another
|
|
143
|
+
// tool, or a symlink pointing elsewhere — it holds content or intent we
|
|
144
|
+
// must not silently destroy. Back it up first (symmetric with how a foreign
|
|
145
|
+
// / hand-edited CLAUDE.md is preserved above), then replace.
|
|
146
|
+
let agentsStat;
|
|
74
147
|
try {
|
|
75
|
-
fs.lstatSync(targetAgents);
|
|
76
|
-
fs.unlinkSync(targetAgents);
|
|
148
|
+
agentsStat = fs.lstatSync(targetAgents);
|
|
77
149
|
}
|
|
78
150
|
catch {
|
|
79
|
-
// does not exist
|
|
151
|
+
// does not exist — nothing to preserve.
|
|
152
|
+
}
|
|
153
|
+
if (agentsStat) {
|
|
154
|
+
const pointsToClaude = agentsStat.isSymbolicLink() &&
|
|
155
|
+
fs.readlinkSync(targetAgents) === "CLAUDE.md";
|
|
156
|
+
if (!pointsToClaude) {
|
|
157
|
+
const bak = backupOnce(targetAgents);
|
|
158
|
+
log.warn(`AGENTS.md 不是指向 CLAUDE.md 的软链;已备份到 ${path.basename(bak)} 后替换为软链。`);
|
|
159
|
+
}
|
|
160
|
+
fs.unlinkSync(targetAgents);
|
|
80
161
|
}
|
|
81
162
|
fs.symlinkSync("CLAUDE.md", targetAgents);
|
|
82
163
|
log.ok("AGENTS.md -> CLAUDE.md symlink created");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auriga-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.28.0",
|
|
4
4
|
"description": "Interactive CLI to install Claude Code harness modules (Workflow, Skills, Recommended Skills, Plugins)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"dev": "tsc --watch",
|
|
26
26
|
"start": "node dist/cli.js",
|
|
27
27
|
"pretest": "npm run build",
|
|
28
|
-
"test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
|
|
29
|
-
"test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
|
|
28
|
+
"test": "tsc -p tsconfig.test.json && DEV=1 node --test --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-markers.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/goalify.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
|
|
29
|
+
"test:watch": "tsc -p tsconfig.test.json --watch & node --test --watch --experimental-test-module-mocks dist-test/tests/skills.test.js dist-test/tests/skills-uninstall.test.js dist-test/tests/catalog.test.js dist-test/tests/cli-parse.test.js dist-test/tests/install-nontty.test.js dist-test/tests/preset.test.js dist-test/tests/legacy-menu.test.js dist-test/tests/plugins.test.js dist-test/tests/plugins-uninstall.test.js dist-test/tests/content-fetch.test.js dist-test/tests/utils.test.js dist-test/tests/guide.test.js dist-test/tests/validators.test.js dist-test/tests/entrypoint.test.js dist-test/tests/state.test.js dist-test/tests/server.test.js dist-test/tests/server-auth.test.js dist-test/tests/server-apply.test.js dist-test/tests/apply-handlers.test.js dist-test/tests/ui-fetch.test.js dist-test/tests/workflow-markers.test.js dist-test/tests/workflow-install.test.js dist-test/tests/workflow-uninstall.test.js dist-test/tests/tarball-shape.test.js dist-test/tests/spec-design.test.js dist-test/tests/goalify.test.js dist-test/tests/plugin-skill-frontmatter.test.js",
|
|
30
30
|
"pretest:e2e": "npm run build",
|
|
31
31
|
"test:e2e": "tsc -p tsconfig.test.json && node --test dist-test/tests/e2e-install.test.js",
|
|
32
32
|
"pretest:web-ui-e2e": "npm run build && npm --prefix ui ci && npm --prefix ui run build",
|
|
33
33
|
"test:web-ui-e2e": "tsc -p tsconfig.test.json && node --test dist-test/tests/web-ui-e2e.test.js",
|
|
34
34
|
"test:session-instructions-loader": "node tests/session-instructions-loader.test.mjs",
|
|
35
|
-
"test:git-guards": "node tests/commit-reminder.test.mjs && node tests/pr-create-guard.test.mjs && node tests/pr-ready-guard.test.mjs"
|
|
35
|
+
"test:git-guards": "node tests/commit-reminder.test.mjs && node tests/pr-create-guard.test.mjs && node tests/pr-ready-guard.test.mjs && node tests/pr-merge-guard.test.mjs"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": ">=18"
|