deuk-agent-flow 4.0.19
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/CHANGELOG.ko.md +223 -0
- package/CHANGELOG.md +227 -0
- package/LICENSE +184 -0
- package/README.ko.md +282 -0
- package/README.md +270 -0
- package/bin/deuk-agent-flow.js +50 -0
- package/bin/deuk-agent-rule.js +2 -0
- package/core-rules/AGENTS.md +153 -0
- package/core-rules/GEMINI.md +7 -0
- package/docs/architecture.ko.md +34 -0
- package/docs/architecture.md +33 -0
- package/docs/assets/architecture-v3.png +0 -0
- package/docs/how-it-works.ko.md +52 -0
- package/docs/how-it-works.md +71 -0
- package/docs/principles.ko.md +68 -0
- package/docs/principles.md +68 -0
- package/docs/usage-guide.ko.md +212 -0
- package/package.json +96 -0
- package/scripts/cli-args.mjs +200 -0
- package/scripts/cli-init-commands.mjs +1799 -0
- package/scripts/cli-init-logic.mjs +64 -0
- package/scripts/cli-prompts.mjs +104 -0
- package/scripts/cli-rule-compiler.mjs +112 -0
- package/scripts/cli-skill-commands.mjs +201 -0
- package/scripts/cli-telemetry-commands.mjs +599 -0
- package/scripts/cli-ticket-commands.mjs +2393 -0
- package/scripts/cli-ticket-index.mjs +298 -0
- package/scripts/cli-ticket-migration.mjs +320 -0
- package/scripts/cli-ticket-parser.mjs +209 -0
- package/scripts/cli-usage-commands.mjs +326 -0
- package/scripts/cli-utils.mjs +587 -0
- package/scripts/cli.mjs +246 -0
- package/scripts/lint-md.mjs +267 -0
- package/scripts/lint-rules.mjs +186 -0
- package/scripts/merge-logic.mjs +44 -0
- package/scripts/plan-parser.mjs +53 -0
- package/scripts/publish-dual-npm.mjs +141 -0
- package/scripts/smoke-npm-docker.mjs +102 -0
- package/scripts/smoke-npm-local.mjs +109 -0
- package/scripts/update-download-badge.mjs +107 -0
- package/templates/MODULE_RULE_TEMPLATE.md +11 -0
- package/templates/PROJECT_RULE.md +47 -0
- package/templates/TICKET_TEMPLATE.ko.md +44 -0
- package/templates/TICKET_TEMPLATE.md +44 -0
- package/templates/project-pilot/CONFORMANCE_GATE_TEMPLATE.md +23 -0
- package/templates/project-pilot/DRIFT_CHECKLIST.md +19 -0
- package/templates/project-pilot/FLOW_CONTRACT_TEMPLATE.md +26 -0
- package/templates/project-pilot/IMPLEMENTATION_MATRIX_TEMPLATE.md +30 -0
- package/templates/project-pilot/INTEGRATION_CONTRACT_TEMPLATE.md +26 -0
- package/templates/project-pilot/OWNER_MAP_TEMPLATE.md +15 -0
- package/templates/project-pilot/PROJECT_PILOT_RULE_TEMPLATE.md +34 -0
- package/templates/project-pilot/REFACTOR_CONTRACT_TEMPLATE.md +32 -0
- package/templates/project-pilot/REMEDIATION_PLAN_TEMPLATE.md +33 -0
- package/templates/rules.d/deukcontext-mcp.md +31 -0
- package/templates/rules.d/platform-coexistence.md +29 -0
- package/templates/skills/context-recall/SKILL.md +25 -0
- package/templates/skills/generated-file-guard/SKILL.md +25 -0
- package/templates/skills/project-pilot/SKILL.md +63 -0
- package/templates/skills/safe-refactor/SKILL.md +25 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
const RULE_CHECKS = [
|
|
5
|
+
{
|
|
6
|
+
code: "DR-KERNEL-01",
|
|
7
|
+
message: "Compact kernel must keep ticket-first and tool-contract invariants at the top of the core rules.",
|
|
8
|
+
test: (rules) => /## Compact Kernel/i.test(rules)
|
|
9
|
+
&& /Tools own detail/i.test(rules)
|
|
10
|
+
&& /No ticket, no writes/i.test(rules)
|
|
11
|
+
&& /User requirements are ticket-first/i.test(rules)
|
|
12
|
+
&& /Existing-ticket close actions are not new-ticket triggers/i.test(rules)
|
|
13
|
+
&& /cause, analysis, and design\/approach/i.test(rules)
|
|
14
|
+
&& /approval or correction creates another ticket update loop/i.test(rules)
|
|
15
|
+
&& /reopen and review the durable ticket body/i.test(rules)
|
|
16
|
+
&& /wait for explicit user approval/i.test(rules)
|
|
17
|
+
&& /Every phase must request and satisfy the tool-provided contract/i.test(rules)
|
|
18
|
+
&& /Phase state has two records/i.test(rules)
|
|
19
|
+
&& /deuk-agent-flow ticket guard/i.test(rules)
|
|
20
|
+
&& /explicit approval validated by `deuk-agent-flow ticket guard`/i.test(rules)
|
|
21
|
+
&& /Verification is mandatory/i.test(rules)
|
|
22
|
+
&& /Completion reports go in the ticket first/i.test(rules)
|
|
23
|
+
&& /terse TDW feedback only/i.test(rules)
|
|
24
|
+
&& /never bypass ticket, scope, generated-file, or verification gates/i.test(rules)
|
|
25
|
+
&& /Shortcut regression guard/i.test(rules)
|
|
26
|
+
&& /temporary passes, bypasses, semantic shrinkage, and language-specific patch branches/i.test(rules)
|
|
27
|
+
&& /Shared-contract guard/i.test(rules)
|
|
28
|
+
&& /Ticket creation failures are hard stops/i.test(rules)
|
|
29
|
+
&& /do not call `set_workflow_context`/i.test(rules)
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
code: "DR-TOKEN-01",
|
|
33
|
+
message: "Low-token mode must stay quiet and compact.",
|
|
34
|
+
test: (rules) => /Silent-by-default is mandatory/i.test(rules)
|
|
35
|
+
&& /Keep chat compact/i.test(rules)
|
|
36
|
+
&& /Final answers must be short but complete enough/i.test(rules)
|
|
37
|
+
&& /Commentary surface map/i.test(rules)
|
|
38
|
+
&& /Running-surface contract/i.test(rules)
|
|
39
|
+
&& /CLI running-output contract/i.test(rules)
|
|
40
|
+
&& /must not print narrative labels, usage reminders, `file:\/\/` links, or progress text/i.test(rules)
|
|
41
|
+
&& /Shared interrupt contract/i.test(rules)
|
|
42
|
+
&& /When the user complains about verbosity, chatter, progress reports, or over-explaining/i.test(rules)
|
|
43
|
+
&& /Do not switch into meta labeling, terminology lessons, or general explanation/i.test(rules)
|
|
44
|
+
&& /짧게|매우 짧게|한 줄로|간단히/i.test(rules)
|
|
45
|
+
&& /one-sentence or bullet-only/i.test(rules)
|
|
46
|
+
&& /avoid repeating them in chat/i.test(rules)
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
code: "DR-PRIORITY-01",
|
|
50
|
+
message: "Rules must define pointer/core/project instruction precedence.",
|
|
51
|
+
test: (rules) => /## 0\. Priority/i.test(rules)
|
|
52
|
+
&& /Global DeukAgentFlow pointer/i.test(rules)
|
|
53
|
+
&& /Local generated pointer\/spoke/i.test(rules)
|
|
54
|
+
&& /core-rules\/AGENTS\.md/i.test(rules)
|
|
55
|
+
&& /PROJECT_RULE\.md/i.test(rules)
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
code: "DR-BOOT-01",
|
|
59
|
+
message: "Boot sequence must load core rules, project rules, and ticket context.",
|
|
60
|
+
test: (rules) => /Boot Sequence \(run once\)/i.test(rules)
|
|
61
|
+
&& /Read this file \(AGENTS\.md\)/i.test(rules)
|
|
62
|
+
&& /Read `PROJECT_RULE\.md`/i.test(rules)
|
|
63
|
+
&& /ticket-start line, reopen and review the durable ticket body, and stop while approval is pending/i.test(rules)
|
|
64
|
+
&& /approval\/correction in the ticket/i.test(rules)
|
|
65
|
+
&& /deuk-agent-flow ticket guard --topic <id> --ticket-started --ticket-reviewed --approval approved/i.test(rules)
|
|
66
|
+
&& /set_workflow_context\(project, ticket_id, phase\)/i.test(rules)
|
|
67
|
+
&& /explicit user approval/i.test(rules)
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
code: "DR-TICKET-01",
|
|
71
|
+
message: "First-turn and discovery behavior must stay ticket-first.",
|
|
72
|
+
test: (rules) => /First-Turn Invariant/i.test(rules)
|
|
73
|
+
&& /Ticket Discovery \(1-CALL RULE\)/i.test(rules)
|
|
74
|
+
&& /create the ticket first/i.test(rules)
|
|
75
|
+
&& /reopen and review the durable ticket body/i.test(rules)
|
|
76
|
+
&& /wait for explicit user approval/i.test(rules)
|
|
77
|
+
&& /update the ticket with that new input/i.test(rules)
|
|
78
|
+
&& /If that correction arrives during `approved_execution`, `command_running`, or `search_running`, treat it as an immediate interrupt/i.test(rules)
|
|
79
|
+
&& /If the user asks only for commit\/report\/archive\/close/i.test(rules)
|
|
80
|
+
&& /reuse that ticket and continue through Phase 4/i.test(rules)
|
|
81
|
+
&& /create a new ticket only if the user adds new implementation, new investigation, or a broader scope/i.test(rules)
|
|
82
|
+
&& /deuk-agent-flow ticket guard --ticket-started --ticket-reviewed --approval approved/i.test(rules)
|
|
83
|
+
&& /do not run repo inspection commands/i.test(rules)
|
|
84
|
+
&& /deuk-agent-flow ticket create` or `deuk-agent-flow ticket use/i.test(rules)
|
|
85
|
+
&& /Do not start with `git status`, `rg`, `find`, diffs, or broad help output/i.test(rules)
|
|
86
|
+
&& /Do not use `deuk-agent-flow ticket list` for discovery/i.test(rules)
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
code: "DR-CHANGE-01",
|
|
90
|
+
message: "Phase contract must require tool-provided requirements and block shortcuts.",
|
|
91
|
+
test: (rules) => /Phase Contract/i.test(rules)
|
|
92
|
+
&& /complete requirement bundle/i.test(rules)
|
|
93
|
+
&& /Required ticket fields\/tasks/i.test(rules)
|
|
94
|
+
&& /Scope boundaries, generated\/source mapping/i.test(rules)
|
|
95
|
+
&& /Do not invent a shortcut/i.test(rules)
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
code: "DR-LIFECYCLE-01",
|
|
99
|
+
message: "Lifecycle must cover phases, durable records, and compact chat.",
|
|
100
|
+
test: (rules) => /Ticket Lifecycle/i.test(rules)
|
|
101
|
+
&& /Phase 0/i.test(rules)
|
|
102
|
+
&& /Phase 4/i.test(rules)
|
|
103
|
+
&& /cause, analysis, design\/approach/i.test(rules)
|
|
104
|
+
&& /findings, hypotheses, scope, compact plan, and phase contract/i.test(rules)
|
|
105
|
+
&& /verification outcome, completion report, residual risk, and a follow-up decision/i.test(rules)
|
|
106
|
+
&& /Keep chat compact once the ticket carries the durable record/i.test(rules)
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
code: "DR-GATE-01",
|
|
110
|
+
message: "Hard stops must block missing contracts, unsafe scope, and premature execution.",
|
|
111
|
+
test: (rules) => /Hard Stops/i.test(rules)
|
|
112
|
+
&& /missing phase contract/i.test(rules)
|
|
113
|
+
&& /generated\/source uncertainty/i.test(rules)
|
|
114
|
+
&& /shared-interface changes/i.test(rules)
|
|
115
|
+
&& /Stop for shortcut regressions/i.test(rules)
|
|
116
|
+
&& /verification that proves only the workaround instead of the shared contract/i.test(rules)
|
|
117
|
+
&& /repo inspection started before ticket selection or creation/i.test(rules)
|
|
118
|
+
&& /same TDW failure family appears twice/i.test(rules)
|
|
119
|
+
&& /read-only until the ticket records findings/i.test(rules)
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
code: "DR-HALT-01",
|
|
123
|
+
message: "Kernel must still define halt conditions and file guards.",
|
|
124
|
+
test: (rules) => /Hard Stops/i.test(rules)
|
|
125
|
+
&& /generated\/source uncertainty/i.test(rules)
|
|
126
|
+
&& /missing tests/i.test(rules)
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
code: "DR-CHURN-01",
|
|
130
|
+
message: "Repeated symptom fixes must trigger stabilization instead of ticket churn.",
|
|
131
|
+
test: (rules) => /Hard Stops/i.test(rules)
|
|
132
|
+
&& /stabilization or root-cause ticket/i.test(rules)
|
|
133
|
+
&& /same failure family/i.test(rules)
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
code: "DR-CLI-01",
|
|
137
|
+
message: "Tool delegation and CLI ownership must stay explicit.",
|
|
138
|
+
test: (rules) => /Tool Delegation/i.test(rules)
|
|
139
|
+
&& /Use `rg`\/`rg --files` first/i.test(rules)
|
|
140
|
+
&& /Use MCP\/RAG only when local evidence is insufficient/i.test(rules)
|
|
141
|
+
&& /Let CLI own lifecycle enforcement, claim checks, reports, and audits/i.test(rules)
|
|
142
|
+
&& /AgentFlow Skill Status/i.test(rules)
|
|
143
|
+
&& /deuk-agent-flow skill list/i.test(rules)
|
|
144
|
+
&& /rules audit/i.test(rules)
|
|
145
|
+
}
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
export function auditRules(cwd = process.cwd()) {
|
|
149
|
+
const rulesPath = join(cwd, "core-rules", "AGENTS.md");
|
|
150
|
+
if (!existsSync(rulesPath)) {
|
|
151
|
+
return {
|
|
152
|
+
ok: false,
|
|
153
|
+
path: rulesPath,
|
|
154
|
+
violations: [{ code: "DR-RULES-00", message: "core-rules/AGENTS.md not found" }]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const rules = readFileSync(rulesPath, "utf8");
|
|
159
|
+
const violations = RULE_CHECKS
|
|
160
|
+
.filter((check) => !check.test(rules))
|
|
161
|
+
.map(({ code, message }) => ({ code, message }));
|
|
162
|
+
|
|
163
|
+
return { ok: violations.length === 0, path: rulesPath, violations };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function runRulesAudit(opts = {}) {
|
|
167
|
+
const result = auditRules(opts.cwd || process.cwd());
|
|
168
|
+
if (opts.json) {
|
|
169
|
+
console.log(JSON.stringify(result, null, 2));
|
|
170
|
+
} else if (opts.compact) {
|
|
171
|
+
console.log(result.ok ? "rules:audit ok" : `rules:audit failed ${result.violations.length}`);
|
|
172
|
+
} else if (result.ok) {
|
|
173
|
+
console.log("rules:audit ok");
|
|
174
|
+
} else {
|
|
175
|
+
console.error("rules:audit failed");
|
|
176
|
+
for (const violation of result.violations) {
|
|
177
|
+
console.error(`${violation.code}: ${violation.message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!result.ok) {
|
|
182
|
+
throw new Error(`rules audit failed: ${result.violations.map((v) => v.code).join(", ")}`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readFileSync,
|
|
4
|
+
} from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_TAG = "DeukAgentFlow";
|
|
8
|
+
|
|
9
|
+
export function readBundleAgents(bundleRoot) {
|
|
10
|
+
const p = join(bundleRoot, "core-rules", "AGENTS.md");
|
|
11
|
+
if (!existsSync(p)) {
|
|
12
|
+
throw new Error("Bundle AGENTS.md missing: " + p);
|
|
13
|
+
}
|
|
14
|
+
return readFileSync(p, "utf8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function removeTaggedBlock(content, begin, end) {
|
|
18
|
+
const i = content.indexOf(begin);
|
|
19
|
+
if (i === -1) {
|
|
20
|
+
return { ok: false, reason: "begin not found" };
|
|
21
|
+
}
|
|
22
|
+
if (end === null) {
|
|
23
|
+
let blockStart = i;
|
|
24
|
+
const prevText = content.slice(0, i);
|
|
25
|
+
const hrIndex = prevText.lastIndexOf("---");
|
|
26
|
+
if (hrIndex !== -1 && prevText.slice(hrIndex).trim() === "") {
|
|
27
|
+
blockStart = hrIndex;
|
|
28
|
+
}
|
|
29
|
+
let next = content.slice(0, blockStart).trimEnd() + "\n";
|
|
30
|
+
return { ok: true, content: next };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const j = content.indexOf(end, i + begin.length);
|
|
34
|
+
if (j === -1) {
|
|
35
|
+
return { ok: false, reason: "end not found" };
|
|
36
|
+
}
|
|
37
|
+
const afterEnd = j + end.length;
|
|
38
|
+
let next = content.slice(0, i) + content.slice(afterEnd);
|
|
39
|
+
next = next.replace(/\n{3,}/g, "\n\n").replace(/^\n+/, "").trimEnd();
|
|
40
|
+
if (next.length) {
|
|
41
|
+
next += "\n";
|
|
42
|
+
}
|
|
43
|
+
return { ok: true, content: next };
|
|
44
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Markdown Plan Parser
|
|
3
|
+
*
|
|
4
|
+
* Supports various AI Agent Plan formats (Antigravity, Copilot, etc.)
|
|
5
|
+
* Extracts basic ticket fields (title, summary, tasks) and body.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function parseGenericMarkdownPlan(content) {
|
|
9
|
+
const lines = content.split('\n');
|
|
10
|
+
|
|
11
|
+
// 1. Title: First H1
|
|
12
|
+
const h1Line = lines.find(l => /^# /.test(l));
|
|
13
|
+
const title = h1Line ? h1Line.replace(/^# /, '').trim() : '';
|
|
14
|
+
|
|
15
|
+
// 2. Summary: Text between H1 and the first --- or ##
|
|
16
|
+
const h1Idx = h1Line ? lines.indexOf(h1Line) : -1;
|
|
17
|
+
let summaryLines = [];
|
|
18
|
+
for (let i = h1Idx + 1; i < lines.length; i++) {
|
|
19
|
+
if (/^---$/.test(lines[i].trim()) || /^## /.test(lines[i])) break;
|
|
20
|
+
if (lines[i].trim()) summaryLines.push(lines[i].trim());
|
|
21
|
+
}
|
|
22
|
+
const summary = summaryLines.join(' ').slice(0, 200);
|
|
23
|
+
|
|
24
|
+
// 3. Tasks: Collect all checklist items
|
|
25
|
+
const tasks = lines
|
|
26
|
+
.filter(l => /^\s*- \[[ x/]\]/.test(l))
|
|
27
|
+
.map(l => l.replace(/^\s*- \[[ x/]\]\s*/, '').trim());
|
|
28
|
+
|
|
29
|
+
return { title, summary, tasks, body: content };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect agent type from plan path
|
|
34
|
+
*/
|
|
35
|
+
export function detectAgentFromPath(planPath) {
|
|
36
|
+
if (planPath.includes('.gemini')) return 'antigravity';
|
|
37
|
+
if (planPath.includes('copilot') || planPath.includes('.codex')) return 'copilot';
|
|
38
|
+
if (planPath.includes('.cursor')) return 'cursor';
|
|
39
|
+
if (planPath.includes('.claude')) return 'claude';
|
|
40
|
+
return 'generic';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const PLAN_PARSERS = {
|
|
44
|
+
'antigravity': parseGenericMarkdownPlan,
|
|
45
|
+
'copilot': parseGenericMarkdownPlan,
|
|
46
|
+
'generic': parseGenericMarkdownPlan
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function parsePlan(planPath, content) {
|
|
50
|
+
const agent = detectAgentFromPath(planPath);
|
|
51
|
+
const parser = PLAN_PARSERS[agent] || parseGenericMarkdownPlan;
|
|
52
|
+
return parser(content);
|
|
53
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const rootDir = join(__dirname, "..");
|
|
9
|
+
const aliasDir = join(rootDir, "packages", "deuk-agent-rule");
|
|
10
|
+
|
|
11
|
+
export function buildAliasPackageJson(rootPkg, currentAliasPkg = {}) {
|
|
12
|
+
const version = rootPkg.version;
|
|
13
|
+
return {
|
|
14
|
+
...currentAliasPkg,
|
|
15
|
+
name: "deuk-agent-rule",
|
|
16
|
+
version,
|
|
17
|
+
description: "Compatibility wrapper for legacy deuk-agent-rule installs.",
|
|
18
|
+
keywords: [
|
|
19
|
+
"agents-md",
|
|
20
|
+
"agent-flow",
|
|
21
|
+
"agent-workflow",
|
|
22
|
+
"ai-guardrails",
|
|
23
|
+
"agent-guardrails",
|
|
24
|
+
"deuk-family",
|
|
25
|
+
],
|
|
26
|
+
license: rootPkg.license || currentAliasPkg.license || "Apache-2.0",
|
|
27
|
+
repository: rootPkg.repository,
|
|
28
|
+
bugs: rootPkg.bugs,
|
|
29
|
+
homepage: rootPkg.homepage,
|
|
30
|
+
files: ["bin/**/*", "README.md"],
|
|
31
|
+
bin: {
|
|
32
|
+
"deuk-agent-rule": "./bin/deuk-agent-rule.js",
|
|
33
|
+
deukagentrule: "./bin/deuk-agent-rule.js",
|
|
34
|
+
},
|
|
35
|
+
engines: rootPkg.engines || currentAliasPkg.engines || { node: ">=18" },
|
|
36
|
+
dependencies: {
|
|
37
|
+
"deuk-agent-flow": version,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readJson(path) {
|
|
43
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeJson(path, value) {
|
|
47
|
+
writeFileSync(path, JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function run(command, args, cwd) {
|
|
51
|
+
const shown = [command, ...args].join(" ");
|
|
52
|
+
console.log(`\n$ ${shown}`);
|
|
53
|
+
const result = spawnSync(command, args, { cwd, stdio: "inherit" });
|
|
54
|
+
if (result.status !== 0) {
|
|
55
|
+
process.exit(result.status || 1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function syncAliasPackageJson(paths = {}) {
|
|
60
|
+
const rootPackagePath = paths.rootPackagePath || join(rootDir, "package.json");
|
|
61
|
+
const aliasPackagePath = paths.aliasPackagePath || join(aliasDir, "package.json");
|
|
62
|
+
const rootPkg = readJson(rootPackagePath);
|
|
63
|
+
const aliasPkg = readJson(aliasPackagePath);
|
|
64
|
+
const nextAliasPkg = buildAliasPackageJson(rootPkg, aliasPkg);
|
|
65
|
+
writeJson(aliasPackagePath, nextAliasPkg);
|
|
66
|
+
return nextAliasPkg;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function parseArgs(argv) {
|
|
70
|
+
const opts = {
|
|
71
|
+
dryRun: false,
|
|
72
|
+
access: "public",
|
|
73
|
+
tag: null,
|
|
74
|
+
otp: null,
|
|
75
|
+
skipTests: false,
|
|
76
|
+
skipSmoke: false,
|
|
77
|
+
aliasOnly: false,
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
81
|
+
const arg = argv[i];
|
|
82
|
+
if (arg === "--dry-run") opts.dryRun = true;
|
|
83
|
+
else if (arg === "--skip-tests") opts.skipTests = true;
|
|
84
|
+
else if (arg === "--skip-smoke") opts.skipSmoke = true;
|
|
85
|
+
else if (arg === "--alias-only") opts.aliasOnly = true;
|
|
86
|
+
else if (arg === "--access") opts.access = argv[++i];
|
|
87
|
+
else if (arg === "--tag") opts.tag = argv[++i];
|
|
88
|
+
else if (arg === "--otp") opts.otp = argv[++i];
|
|
89
|
+
else {
|
|
90
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return opts;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function publishArgs(opts) {
|
|
98
|
+
const args = ["publish", "--access", opts.access, "--ignore-scripts"];
|
|
99
|
+
if (opts.tag) args.push("--tag", opts.tag);
|
|
100
|
+
if (opts.otp) args.push("--otp", opts.otp);
|
|
101
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
102
|
+
return args;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function main(argv = process.argv.slice(2)) {
|
|
106
|
+
if (process.env.npm_command === "publish" && process.env.npm_lifecycle_event === "publish") {
|
|
107
|
+
console.log("Skipping scripts.publish during npm publish lifecycle.");
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const opts = parseArgs(argv);
|
|
112
|
+
const aliasPkg = syncAliasPackageJson();
|
|
113
|
+
|
|
114
|
+
const packageLabel = opts.aliasOnly ? "deuk-agent-rule" : "deuk-agent-flow and deuk-agent-rule";
|
|
115
|
+
console.log(`Publishing ${packageLabel} at ${aliasPkg.version}`);
|
|
116
|
+
if (opts.dryRun) {
|
|
117
|
+
console.log("Dry-run mode: npm will build and validate packages without registry writes.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!opts.skipTests) {
|
|
121
|
+
run("npm", ["test"], rootDir);
|
|
122
|
+
}
|
|
123
|
+
if (!opts.skipSmoke) {
|
|
124
|
+
run("node", ["scripts/smoke-npm-local.mjs"], rootDir);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const args = publishArgs(opts);
|
|
128
|
+
if (!opts.aliasOnly) {
|
|
129
|
+
run("npm", args, rootDir);
|
|
130
|
+
}
|
|
131
|
+
run("npm", args, aliasDir);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
135
|
+
try {
|
|
136
|
+
main();
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error(error.message);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from "fs";
|
|
3
|
+
import { join, resolve } from "path";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
|
|
7
|
+
const rootDir = resolve(new URL("..", import.meta.url).pathname);
|
|
8
|
+
const aliasDir = join(rootDir, "packages", "deuk-agent-rule");
|
|
9
|
+
const workDir = mkdtempSync(join(tmpdir(), "deuk-agent-flow-docker-smoke-"));
|
|
10
|
+
|
|
11
|
+
function run(command, args, cwd, opts = {}) {
|
|
12
|
+
const shown = [command, ...args].join(" ");
|
|
13
|
+
console.log(`\n$ ${shown}`);
|
|
14
|
+
const result = spawnSync(command, args, {
|
|
15
|
+
cwd,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: opts.capture ? "pipe" : "inherit",
|
|
18
|
+
});
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
if (opts.capture) {
|
|
21
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
22
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`command failed: ${shown}`);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readJson(path) {
|
|
30
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function pack(cwd) {
|
|
34
|
+
const result = run("npm", ["pack", "--json", "--pack-destination", workDir], cwd, { capture: true });
|
|
35
|
+
const rows = JSON.parse(result.stdout);
|
|
36
|
+
const filename = rows[0]?.filename;
|
|
37
|
+
if (!filename) throw new Error(`npm pack did not return a filename for ${cwd}`);
|
|
38
|
+
const tarball = join(workDir, filename);
|
|
39
|
+
if (!existsSync(tarball)) throw new Error(`packed tarball missing: ${tarball}`);
|
|
40
|
+
return filename;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const rootPkg = readJson(join(rootDir, "package.json"));
|
|
45
|
+
const aliasPkg = readJson(join(aliasDir, "package.json"));
|
|
46
|
+
|
|
47
|
+
if (rootPkg.name !== "deuk-agent-flow") {
|
|
48
|
+
throw new Error(`expected root package deuk-agent-flow, got ${rootPkg.name}`);
|
|
49
|
+
}
|
|
50
|
+
if (aliasPkg.name !== "deuk-agent-rule") {
|
|
51
|
+
throw new Error(`expected legacy package deuk-agent-rule, got ${aliasPkg.name}`);
|
|
52
|
+
}
|
|
53
|
+
if (aliasPkg.dependencies?.["deuk-agent-flow"] !== rootPkg.version) {
|
|
54
|
+
throw new Error(`deuk-agent-rule must depend on deuk-agent-flow@${rootPkg.version}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const flowTarball = pack(rootDir);
|
|
58
|
+
const ruleTarball = pack(aliasDir);
|
|
59
|
+
const image = process.env.DEUK_SMOKE_NODE_IMAGE || "node:20-bookworm";
|
|
60
|
+
const keepDir = Boolean(process.env.DEUK_KEEP_SMOKE_DIR);
|
|
61
|
+
|
|
62
|
+
console.log(`\nSmoke sandbox: ${workDir}`);
|
|
63
|
+
console.log(`Docker image: ${image}`);
|
|
64
|
+
|
|
65
|
+
const script = [
|
|
66
|
+
"set -eu",
|
|
67
|
+
"node --version",
|
|
68
|
+
"npm --version",
|
|
69
|
+
"mkdir -p /tmp/consumer",
|
|
70
|
+
"npm install -g /pkg/" + flowTarball + " /pkg/" + ruleTarball,
|
|
71
|
+
"command -v deuk-agent-flow",
|
|
72
|
+
"command -v deuk-agent-rule",
|
|
73
|
+
"deuk-agent-flow --help >/tmp/deuk-agent-flow-help.txt",
|
|
74
|
+
"deuk-agent-rule --help >/tmp/deuk-agent-rule-help.txt",
|
|
75
|
+
"cd /tmp/consumer",
|
|
76
|
+
"mkdir -p .codex",
|
|
77
|
+
"deuk-agent-flow init --non-interactive --workflow execute --docs-language ko --agents skip",
|
|
78
|
+
"test -f .codex/AGENTS.md",
|
|
79
|
+
"test -f PROJECT_RULE.md",
|
|
80
|
+
"test -d .deuk-agent",
|
|
81
|
+
"test -d .deuk-agent/templates",
|
|
82
|
+
"test -d .deuk-agent/skill-templates",
|
|
83
|
+
].join("\n");
|
|
84
|
+
|
|
85
|
+
run("docker", [
|
|
86
|
+
"run",
|
|
87
|
+
"--rm",
|
|
88
|
+
"-v",
|
|
89
|
+
`${workDir}:/pkg:ro`,
|
|
90
|
+
image,
|
|
91
|
+
"bash",
|
|
92
|
+
"-lc",
|
|
93
|
+
script,
|
|
94
|
+
], rootDir);
|
|
95
|
+
|
|
96
|
+
console.log("\nDocker npm smoke ok");
|
|
97
|
+
if (keepDir) console.log(`Kept smoke sandbox: ${workDir}`);
|
|
98
|
+
} finally {
|
|
99
|
+
if (!process.env.DEUK_KEEP_SMOKE_DIR) {
|
|
100
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, statSync } from "fs";
|
|
3
|
+
import { delimiter, join, resolve } from "path";
|
|
4
|
+
import { spawnSync } from "child_process";
|
|
5
|
+
import { tmpdir } from "os";
|
|
6
|
+
|
|
7
|
+
const rootDir = resolve(new URL("..", import.meta.url).pathname);
|
|
8
|
+
const aliasDir = join(rootDir, "packages", "deuk-agent-rule");
|
|
9
|
+
const workDir = mkdtempSync(join(tmpdir(), "deuk-agent-flow-local-smoke-"));
|
|
10
|
+
const prefixDir = join(workDir, "prefix");
|
|
11
|
+
|
|
12
|
+
function run(command, args, cwd, opts = {}) {
|
|
13
|
+
const shown = [command, ...args].join(" ");
|
|
14
|
+
console.log(`\n$ ${shown}`);
|
|
15
|
+
const result = spawnSync(command, args, {
|
|
16
|
+
cwd,
|
|
17
|
+
encoding: "utf8",
|
|
18
|
+
env: opts.env || process.env,
|
|
19
|
+
stdio: opts.capture ? "pipe" : "inherit",
|
|
20
|
+
});
|
|
21
|
+
if (result.status !== 0) {
|
|
22
|
+
if (opts.capture) {
|
|
23
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
24
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`command failed: ${shown}`);
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readJson(path) {
|
|
32
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function pack(cwd, expected) {
|
|
36
|
+
const result = run("npm", ["pack", "--json", "--pack-destination", workDir], cwd, { capture: true });
|
|
37
|
+
const rows = JSON.parse(result.stdout);
|
|
38
|
+
const row = rows[0];
|
|
39
|
+
const filename = row?.filename;
|
|
40
|
+
if (!filename) throw new Error(`npm pack did not return a filename for ${cwd}`);
|
|
41
|
+
const tarball = join(workDir, filename);
|
|
42
|
+
if (!existsSync(tarball)) throw new Error(`packed tarball missing: ${tarball}`);
|
|
43
|
+
|
|
44
|
+
const fileSet = new Set((row.files || []).map(file => file.path));
|
|
45
|
+
for (const path of expected.files) {
|
|
46
|
+
if (!fileSet.has(path)) throw new Error(`${expected.name} tarball missing required file: ${path}`);
|
|
47
|
+
}
|
|
48
|
+
return { filename, tarball };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function globalBinDir(prefix) {
|
|
52
|
+
return process.platform === "win32" ? prefix : join(prefix, "bin");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function assertGlobalCommand(binDir, name) {
|
|
56
|
+
const script = process.platform === "win32" ? join(binDir, `${name}.cmd`) : join(binDir, name);
|
|
57
|
+
if (!existsSync(script)) throw new Error(`global command shim missing: ${script}`);
|
|
58
|
+
if (process.platform !== "win32") {
|
|
59
|
+
const mode = statSync(script).mode;
|
|
60
|
+
if ((mode & 0o111) === 0) throw new Error(`global command is not executable: ${script}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const rootPkg = readJson(join(rootDir, "package.json"));
|
|
66
|
+
const aliasPkg = readJson(join(aliasDir, "package.json"));
|
|
67
|
+
if (aliasPkg.dependencies?.["deuk-agent-flow"] !== rootPkg.version) {
|
|
68
|
+
throw new Error(`deuk-agent-rule must depend on deuk-agent-flow@${rootPkg.version}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const flow = pack(rootDir, {
|
|
72
|
+
name: "deuk-agent-flow",
|
|
73
|
+
files: [
|
|
74
|
+
"bin/deuk-agent-flow.js",
|
|
75
|
+
"bin/deuk-agent-rule.js",
|
|
76
|
+
"scripts/cli.mjs",
|
|
77
|
+
"scripts/cli-ticket-commands.mjs",
|
|
78
|
+
"scripts/lint-md.mjs",
|
|
79
|
+
"scripts/lint-rules.mjs",
|
|
80
|
+
"package.json",
|
|
81
|
+
],
|
|
82
|
+
});
|
|
83
|
+
const rule = pack(aliasDir, {
|
|
84
|
+
name: "deuk-agent-rule",
|
|
85
|
+
files: [
|
|
86
|
+
"bin/deuk-agent-rule.js",
|
|
87
|
+
"package.json",
|
|
88
|
+
"README.md",
|
|
89
|
+
],
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
run("npm", ["install", "-g", "--prefix", prefixDir, flow.tarball, rule.tarball], rootDir);
|
|
93
|
+
|
|
94
|
+
const binDir = globalBinDir(prefixDir);
|
|
95
|
+
const pathEnv = [binDir, process.env.PATH || ""].join(delimiter);
|
|
96
|
+
const env = { ...process.env, PATH: pathEnv };
|
|
97
|
+
|
|
98
|
+
for (const command of ["deuk-agent-flow", "deukagentflow", "deuk-agent-rule", "deukagentrule"]) {
|
|
99
|
+
assertGlobalCommand(binDir, command);
|
|
100
|
+
run(command, ["--help"], rootDir, { env, capture: true });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(`\nLocal npm install smoke ok (${process.platform})`);
|
|
104
|
+
console.log(`Checked tarballs: ${flow.filename}, ${rule.filename}`);
|
|
105
|
+
} finally {
|
|
106
|
+
if (!process.env.DEUK_KEEP_SMOKE_DIR) {
|
|
107
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
108
|
+
}
|
|
109
|
+
}
|