deuk-agent-flow 4.2.7 → 5.0.4
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 +277 -0
- package/CHANGELOG.md +794 -124
- package/LICENSE +0 -0
- package/README.ko.md +97 -17
- package/README.md +108 -25
- package/bin/deuk-agent-flow.js +11 -13
- package/bin/deuk-agent-rule.js +1 -1
- package/bundled/README.md +3 -0
- package/bundled/deuk-agent-flow.vsix +0 -0
- package/core-rules/AGENTS.md +30 -120
- package/docs/architecture.ko.md +155 -2
- package/docs/architecture.md +155 -2
- package/docs/assets/agentflow-panel-skills.png +0 -0
- package/docs/assets/agentflow-panel.png +0 -0
- package/docs/how-it-works.ko.md +109 -52
- package/docs/how-it-works.md +128 -71
- package/docs/principles.ko.md +68 -68
- package/docs/principles.md +68 -68
- package/docs/usage-guide.ko.md +251 -212
- package/package.json +60 -45
- package/scripts/bundle-vscode-vsix.ts +67 -0
- package/scripts/{cli-args.mjs → cli-args.ts} +49 -12
- package/scripts/cli-context-commands.ts +35 -0
- package/scripts/cli-init-claude.ts +179 -0
- package/scripts/cli-init-commands.ts +99 -0
- package/scripts/cli-init-core.ts +89 -0
- package/scripts/cli-init-instructions.ts +489 -0
- package/scripts/cli-init-logic.ts +46 -0
- package/scripts/cli-init-migrate.ts +504 -0
- package/scripts/cli-init-workspace.ts +63 -0
- package/scripts/cli-locales.ts +85 -0
- package/scripts/{cli-prompts.mjs → cli-prompts.ts} +19 -34
- package/scripts/{cli-rule-compiler.mjs → cli-rule-compiler.ts} +128 -112
- package/scripts/cli-skill-commands.ts +709 -0
- package/scripts/{cli-telemetry-commands.mjs → cli-telemetry-commands.ts} +25 -22
- package/scripts/cli-ticket-command-shared.ts +4 -0
- package/scripts/cli-ticket-commands.ts +3781 -0
- package/scripts/cli-ticket-docmeta.ts +103 -0
- package/scripts/cli-ticket-document.ts +153 -0
- package/scripts/cli-ticket-evidence.ts +312 -0
- package/scripts/cli-ticket-home.ts +449 -0
- package/scripts/cli-ticket-index.ts +283 -0
- package/scripts/{cli-ticket-migration.mjs → cli-ticket-migration.ts} +44 -68
- package/scripts/cli-ticket-parser.ts +100 -0
- package/scripts/cli-ticket-scan.ts +102 -0
- package/scripts/cli-ticket-surface.ts +56 -0
- package/scripts/{cli-usage-commands.mjs → cli-usage-commands.ts} +325 -326
- package/scripts/cli-utils.ts +1560 -0
- package/scripts/cli.ts +695 -0
- package/scripts/{lint-md.mjs → lint-md.ts} +32 -42
- package/scripts/lint-rules.ts +203 -0
- package/scripts/{merge-logic.mjs → merge-logic.ts} +44 -44
- package/scripts/oss-prepack.js +32 -0
- package/scripts/out/scripts/bundle-vscode-vsix.js +68 -0
- package/scripts/out/scripts/bundle-vscode-vsix.js.map +1 -0
- package/scripts/out/scripts/cli-args.js +374 -0
- package/scripts/out/scripts/cli-args.js.map +1 -0
- package/scripts/out/scripts/cli-context-commands.js +31 -0
- package/scripts/out/scripts/cli-context-commands.js.map +1 -0
- package/scripts/out/scripts/cli-init-claude.js +176 -0
- package/scripts/out/scripts/cli-init-claude.js.map +1 -0
- package/scripts/out/scripts/cli-init-commands.js +87 -0
- package/scripts/out/scripts/cli-init-commands.js.map +1 -0
- package/scripts/out/scripts/cli-init-core.js +89 -0
- package/scripts/out/scripts/cli-init-core.js.map +1 -0
- package/scripts/out/scripts/cli-init-instructions.js +460 -0
- package/scripts/out/scripts/cli-init-instructions.js.map +1 -0
- package/scripts/out/scripts/cli-init-logic.js +41 -0
- package/scripts/out/scripts/cli-init-logic.js.map +1 -0
- package/scripts/out/scripts/cli-init-migrate.js +497 -0
- package/scripts/out/scripts/cli-init-migrate.js.map +1 -0
- package/scripts/out/scripts/cli-init-workspace.js +57 -0
- package/scripts/out/scripts/cli-init-workspace.js.map +1 -0
- package/scripts/out/scripts/cli-locales.js +83 -0
- package/scripts/out/scripts/cli-locales.js.map +1 -0
- package/scripts/out/scripts/cli-prompts.js +101 -0
- package/scripts/out/scripts/cli-prompts.js.map +1 -0
- package/scripts/out/scripts/cli-rule-compiler.js +121 -0
- package/scripts/out/scripts/cli-rule-compiler.js.map +1 -0
- package/scripts/out/scripts/cli-skill-commands.js +708 -0
- package/scripts/out/scripts/cli-skill-commands.js.map +1 -0
- package/scripts/out/scripts/cli-telemetry-commands.js +604 -0
- package/scripts/out/scripts/cli-telemetry-commands.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-command-shared.js +2 -0
- package/scripts/out/scripts/cli-ticket-command-shared.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-commands.js +3530 -0
- package/scripts/out/scripts/cli-ticket-commands.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-docmeta.js +90 -0
- package/scripts/out/scripts/cli-ticket-docmeta.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-document.js +121 -0
- package/scripts/out/scripts/cli-ticket-document.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-evidence.js +286 -0
- package/scripts/out/scripts/cli-ticket-evidence.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-home.js +440 -0
- package/scripts/out/scripts/cli-ticket-home.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-index.js +272 -0
- package/scripts/out/scripts/cli-ticket-index.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-migration.js +261 -0
- package/scripts/out/scripts/cli-ticket-migration.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-parser.js +90 -0
- package/scripts/out/scripts/cli-ticket-parser.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-scan.js +93 -0
- package/scripts/out/scripts/cli-ticket-scan.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-surface.js +53 -0
- package/scripts/out/scripts/cli-ticket-surface.js.map +1 -0
- package/scripts/out/scripts/cli-usage-commands.js +310 -0
- package/scripts/out/scripts/cli-usage-commands.js.map +1 -0
- package/scripts/out/scripts/cli-utils.js +1544 -0
- package/scripts/out/scripts/cli-utils.js.map +1 -0
- package/scripts/out/scripts/cli.js +696 -0
- package/scripts/out/scripts/cli.js.map +1 -0
- package/scripts/out/scripts/lint-md.js +238 -0
- package/scripts/out/scripts/lint-md.js.map +1 -0
- package/scripts/out/scripts/lint-rules.js +193 -0
- package/scripts/out/scripts/lint-rules.js.map +1 -0
- package/scripts/out/scripts/merge-logic.js +38 -0
- package/scripts/out/scripts/merge-logic.js.map +1 -0
- package/scripts/out/scripts/plan-parser.js +52 -0
- package/scripts/out/scripts/plan-parser.js.map +1 -0
- package/scripts/out/scripts/ticket-machine.js +94 -0
- package/scripts/out/scripts/ticket-machine.js.map +1 -0
- package/scripts/out/scripts/ticket-workflow.js +498 -0
- package/scripts/out/scripts/ticket-workflow.js.map +1 -0
- package/scripts/{plan-parser.mjs → plan-parser.ts} +53 -53
- package/scripts/ticket-machine.ts +99 -0
- package/scripts/ticket-workflow.ts +544 -0
- package/templates/MODULE_RULE_TEMPLATE.md +11 -11
- package/templates/PROJECT_RULE.md +46 -47
- package/templates/TICKET_TEMPLATE.ko.md +48 -44
- package/templates/TICKET_TEMPLATE.md +48 -44
- package/templates/project-memory.md +19 -0
- package/templates/project-pilot/CONFORMANCE_GATE_TEMPLATE.md +25 -25
- package/templates/project-pilot/DRIFT_CHECKLIST.md +27 -27
- package/templates/project-pilot/FLOW_CONTRACT_TEMPLATE.md +26 -26
- package/templates/project-pilot/IMPLEMENTATION_MATRIX_TEMPLATE.md +30 -30
- package/templates/project-pilot/INTEGRATION_CONTRACT_TEMPLATE.md +26 -26
- package/templates/project-pilot/OWNER_MAP_TEMPLATE.md +15 -15
- package/templates/project-pilot/PROJECT_PILOT_RULE_TEMPLATE.md +34 -34
- package/templates/project-pilot/REFACTOR_CONTRACT_TEMPLATE.md +32 -32
- package/templates/project-pilot/REMEDIATION_PLAN_TEMPLATE.md +33 -33
- package/templates/rules.d/deukcontext-mcp.md +31 -31
- package/templates/rules.d/platform-coexistence.md +29 -29
- package/templates/skills/context-recall/SKILL.md +3 -1
- package/templates/skills/doc-sync/SKILL.md +111 -0
- package/templates/skills/generated-file-guard/SKILL.md +3 -1
- package/templates/skills/persona-maid/SKILL.md +65 -0
- package/templates/skills/project-pilot/SKILL.md +14 -63
- package/templates/skills/role-closer/SKILL.md +27 -0
- package/templates/skills/role-implementer/SKILL.md +30 -0
- package/templates/skills/role-planner/SKILL.md +29 -0
- package/templates/skills/role-qa-verifier/SKILL.md +30 -0
- package/templates/skills/safe-refactor/SKILL.md +3 -1
- package/templates/skills/ticket-status-surface/SKILL.md +17 -0
- package/NOTICE.md +0 -19
- package/core-rules/GEMINI.md +0 -7
- package/docs/npm-publish-guide.ko.md +0 -70
- package/scripts/cli-init-commands.mjs +0 -1759
- package/scripts/cli-init-logic.mjs +0 -64
- package/scripts/cli-skill-commands.mjs +0 -212
- package/scripts/cli-ticket-command-shared.mjs +0 -60
- package/scripts/cli-ticket-commands.mjs +0 -2474
- package/scripts/cli-ticket-index.mjs +0 -322
- package/scripts/cli-ticket-parser.mjs +0 -210
- package/scripts/cli-utils.mjs +0 -602
- package/scripts/cli.mjs +0 -256
- package/scripts/lint-rules.mjs +0 -197
- package/scripts/publish-dual-npm.mjs +0 -141
- package/scripts/smoke-npm-docker.mjs +0 -102
- package/scripts/smoke-npm-local.mjs +0 -110
- package/scripts/update-download-badge.mjs +0 -103
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { dirname, resolve, relative } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { CliOpts, DEUK_ROOT_DIR, makePath, resolveConsumerTicketRoot, resolvePackageRoot, toFileUri, toPosixPath } from "./cli-utils.js";
|
|
5
|
+
// #691: 전이/guard 검증은 XState 머신(ticket-machine.mjs)이 담당. ESM 순환 import이지만
|
|
6
|
+
// machineTransition은 호출 시점에만 TICKET_WORKFLOW를 읽으므로 안전하다.
|
|
7
|
+
import { machineTransition } from "./ticket-machine.js";
|
|
8
|
+
|
|
9
|
+
const PACKAGE_ROOT = resolvePackageRoot({ fromUrl: import.meta.url });
|
|
10
|
+
|
|
11
|
+
const TERMINAL_TICKET_STATUSES = new Set(["closed", "completed", "cancelled", "wontfix", "deprecated", "archived"]);
|
|
12
|
+
|
|
13
|
+
// "active" is no longer a status value — a ticket's focus is expressed solely by
|
|
14
|
+
// activeTicketId. status carries lifecycle only: open / archived / closed.
|
|
15
|
+
// Kept "active" here for backward-compat reads of legacy ticket files; new writes use "open".
|
|
16
|
+
export const OPEN_TICKET_STATUSES = new Set(["open", "active"]);
|
|
17
|
+
export const AUTO_ARCHIVE_DONE_STATUSES = new Set(["closed", "completed", "cancelled", "wontfix"]);
|
|
18
|
+
|
|
19
|
+
export function isTerminalTicketStatus(status) {
|
|
20
|
+
return TERMINAL_TICKET_STATUSES.has(String(status || "").toLowerCase());
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const FLOW_SURFACE_STATUS = {
|
|
24
|
+
start: "🌱 시작",
|
|
25
|
+
status: "🔍 조회",
|
|
26
|
+
adjust: "📝 조정",
|
|
27
|
+
progressActive: "⚙️ 진행",
|
|
28
|
+
progressComplete: "✅ 완료",
|
|
29
|
+
end: "🎉 종료"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const TICKET_WORKFLOW = {
|
|
33
|
+
name: "ticket_workflow",
|
|
34
|
+
promptRoot: makePath("docs", "cli-surfaces", "state"),
|
|
35
|
+
templateRoot: makePath("docs", "cli-surfaces", "state-template"),
|
|
36
|
+
policyRoot: makePath(DEUK_ROOT_DIR, "project-guardrails"),
|
|
37
|
+
states: {
|
|
38
|
+
// #690: entry 노드 — rules가 진입 시 라우팅하는 그래프의 시작점들. 프롬프트는
|
|
39
|
+
// docs/cli-surfaces/state/entry-*.md(SSOT). 티켓 phase 노드(phase1~4)와 함께
|
|
40
|
+
// 단일 노드 그래프를 이룬다. router가 cwd/.md 상태로 이 중 하나를 고른다.
|
|
41
|
+
"entry-no-workspace": { prompt: "entry-no-workspace", entry: true, transitions: [], actions: [] },
|
|
42
|
+
"entry-free": { prompt: "entry-free", entry: true, transitions: [], actions: [] },
|
|
43
|
+
"entry-no-active": { prompt: "entry-no-active", entry: true, transitions: ["phase1"], actions: ["create"] },
|
|
44
|
+
"entry-in-progress": { prompt: "entry-in-progress", entry: true, transitions: [], actions: ["use", "move"] },
|
|
45
|
+
start: { prompt: "start", policyRole: "analysis", template: "start", phase: 1, status: "open", transitions: ["phase1", "end"], actions: ["create"] },
|
|
46
|
+
phase1: {
|
|
47
|
+
prompt: "phase1",
|
|
48
|
+
policyRole: "analysis",
|
|
49
|
+
template: "phase1",
|
|
50
|
+
phase: 1,
|
|
51
|
+
status: "open",
|
|
52
|
+
transitions: ["phase2", "end"],
|
|
53
|
+
actions: ["status", "use", "guard", "move", "discard"],
|
|
54
|
+
requiredEvidence: {
|
|
55
|
+
phase2: ["phase1Plan", "userApproval"],
|
|
56
|
+
end: ["phase1Plan"]
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
phase2: {
|
|
60
|
+
prompt: "phase2",
|
|
61
|
+
policyRole: "coding",
|
|
62
|
+
template: "phase2",
|
|
63
|
+
phase: 2,
|
|
64
|
+
status: "open",
|
|
65
|
+
transitions: ["phase3", "end"],
|
|
66
|
+
actions: ["status", "use", "move", "close", "reportAttach"],
|
|
67
|
+
requiredEvidence: {
|
|
68
|
+
phase3: ["executionEvidence"],
|
|
69
|
+
end: ["completionEvidence"]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
phase3: {
|
|
73
|
+
prompt: "phase3",
|
|
74
|
+
policyRole: "debugging",
|
|
75
|
+
template: "phase3",
|
|
76
|
+
phase: 3,
|
|
77
|
+
status: "open",
|
|
78
|
+
transitions: ["phase4", "phase2", "end"],
|
|
79
|
+
actions: ["status", "use", "move", "close", "reportAttach"],
|
|
80
|
+
requiredEvidence: {
|
|
81
|
+
phase4: ["verificationEvidence"],
|
|
82
|
+
phase2: ["debuggingDecision"],
|
|
83
|
+
end: ["completionEvidence"]
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
phase4: {
|
|
87
|
+
prompt: "phase4",
|
|
88
|
+
policyRole: "completion",
|
|
89
|
+
template: "phase4",
|
|
90
|
+
phase: 4,
|
|
91
|
+
status: "closed",
|
|
92
|
+
transitions: ["end", "phase1"],
|
|
93
|
+
actions: ["status", "use", "move", "archive", "reportAttach"],
|
|
94
|
+
requiredEvidence: {
|
|
95
|
+
end: ["completionEvidence"],
|
|
96
|
+
phase1: ["reopenDecision"]
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
end: { prompt: "end", policyRole: "completion", template: "end", phase: 4, status: "closed", transitions: [], actions: ["status", "use", "archive"] },
|
|
100
|
+
useCoreActions: { prompt: "use-core-actions", transitions: [], actions: [] }
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const TICKET_WORKFLOW_EVIDENCE = {
|
|
105
|
+
phase1Plan: {
|
|
106
|
+
label: "phase1_plan_complete",
|
|
107
|
+
missingReason: "phase1_plan_incomplete"
|
|
108
|
+
},
|
|
109
|
+
userApproval: {
|
|
110
|
+
label: "user_approval",
|
|
111
|
+
missingReason: "user_approval_missing"
|
|
112
|
+
},
|
|
113
|
+
executionEvidence: {
|
|
114
|
+
label: "execution_evidence",
|
|
115
|
+
missingReason: "execution_evidence_missing"
|
|
116
|
+
},
|
|
117
|
+
verificationEvidence: {
|
|
118
|
+
label: "verification_evidence",
|
|
119
|
+
missingReason: "verification_evidence_missing"
|
|
120
|
+
},
|
|
121
|
+
debuggingDecision: {
|
|
122
|
+
label: "debugging_decision",
|
|
123
|
+
missingReason: "debugging_decision_missing"
|
|
124
|
+
},
|
|
125
|
+
completionEvidence: {
|
|
126
|
+
label: "completion_evidence",
|
|
127
|
+
missingReason: "completion_evidence_missing"
|
|
128
|
+
},
|
|
129
|
+
reopenDecision: {
|
|
130
|
+
label: "reopen_decision",
|
|
131
|
+
missingReason: "reopen_decision_missing"
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function resolveGuideLanguage(docsLanguage = "ko", resolveLanguage = (value) => value, normalizeDocsLanguage = (value) => value) {
|
|
136
|
+
const normalizedDocsLanguage = normalizeDocsLanguage(docsLanguage);
|
|
137
|
+
return normalizedDocsLanguage === "auto" ? "ko" : resolveLanguage(normalizedDocsLanguage);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveTicketWorkflowState(kind) {
|
|
141
|
+
const state = TICKET_WORKFLOW.states[kind];
|
|
142
|
+
if (!state) {
|
|
143
|
+
throw new Error(`ticket workflow state not declared: ${kind}`);
|
|
144
|
+
}
|
|
145
|
+
return state;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function ticketWorkflowStateNameFromPhase(phase) {
|
|
149
|
+
const normalizedPhase = normalizeTicketPhaseNumber(phase);
|
|
150
|
+
if (normalizedPhase >= 4) return "phase4";
|
|
151
|
+
return `phase${normalizedPhase}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function ticketWorkflowStateNameFromMeta(meta: Record<string, any> = {}, fallbackStatus = "open") {
|
|
155
|
+
const status = deriveTicketWorkflowStatus(meta, fallbackStatus);
|
|
156
|
+
if (TERMINAL_TICKET_STATUSES.has(status)) return "phase4";
|
|
157
|
+
return ticketWorkflowStateNameFromPhase(meta.phase || 1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function statusForTicketWorkflowState(targetStateName, currentStatus = "open") {
|
|
161
|
+
const targetState = resolveTicketWorkflowState(targetStateName);
|
|
162
|
+
const targetPhase = normalizeTicketPhaseNumber(targetState.phase || 1);
|
|
163
|
+
if (targetState.status) return targetState.status;
|
|
164
|
+
if (targetPhase >= 4) return "closed";
|
|
165
|
+
if (targetPhase >= 2) {
|
|
166
|
+
// Execution phases keep status "open"; focus is tracked via activeTicketId.
|
|
167
|
+
const normalizedCurrentStatus = String(currentStatus || "open").toLowerCase();
|
|
168
|
+
return normalizedCurrentStatus === "active" ? "open" : normalizedCurrentStatus;
|
|
169
|
+
}
|
|
170
|
+
return "open";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function resolveTicketWorkflowTransition(meta: Record<string, any> = {}, opts: CliOpts = {}) {
|
|
174
|
+
const currentState = opts.fromState || ticketWorkflowStateNameFromMeta(meta, opts.fallbackStatus);
|
|
175
|
+
const currentWorkflowState = resolveTicketWorkflowState(currentState);
|
|
176
|
+
const targetState = opts.toState
|
|
177
|
+
|| (opts.phase ? ticketWorkflowStateNameFromPhase(opts.phase) : null)
|
|
178
|
+
|| (opts.next ? currentWorkflowState.transitions?.[0] : null);
|
|
179
|
+
if (!targetState) {
|
|
180
|
+
throw new Error(`ticket workflow transition target is required from ${currentState}`);
|
|
181
|
+
}
|
|
182
|
+
const targetWorkflowState = resolveTicketWorkflowState(targetState);
|
|
183
|
+
// #691: 전이 허용 검증은 XState 머신이 담당한다(미정의 전이 차단). 증거 guard는
|
|
184
|
+
// getTicketWorkflowTransitionReasons에서 evidence와 함께 별도로 검증한다.
|
|
185
|
+
const verdict = machineTransition(currentState, targetState);
|
|
186
|
+
if (verdict.reason === "transition_not_declared") {
|
|
187
|
+
throw new Error(`ticket workflow transition blocked: ${currentState} -> ${targetState}`);
|
|
188
|
+
}
|
|
189
|
+
const phase = normalizeTicketPhaseNumber(targetWorkflowState.phase || meta.phase || 1);
|
|
190
|
+
const status = statusForTicketWorkflowState(targetState, meta.status || opts.fallbackStatus);
|
|
191
|
+
return {
|
|
192
|
+
workflow: TICKET_WORKFLOW.name,
|
|
193
|
+
from: currentState,
|
|
194
|
+
to: targetState,
|
|
195
|
+
phase,
|
|
196
|
+
status,
|
|
197
|
+
state: targetWorkflowState
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function getTicketWorkflowTransitionEvidenceKeys(fromStateName, toStateName) {
|
|
202
|
+
const fromState = resolveTicketWorkflowState(fromStateName);
|
|
203
|
+
return fromState.requiredEvidence?.[toStateName] || [];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function resolveTicketWorkflowRuntimeState(meta: Record<string, any> = {}, fallbackStatus = "open") {
|
|
207
|
+
const stateName = ticketWorkflowStateNameFromMeta(meta, fallbackStatus);
|
|
208
|
+
return {
|
|
209
|
+
name: stateName,
|
|
210
|
+
state: resolveTicketWorkflowState(stateName)
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getTicketWorkflowActionReasons(meta: Record<string, any> = {}, opts: CliOpts = {}) {
|
|
215
|
+
const action = String(opts.action || "").trim();
|
|
216
|
+
const { name, state } = resolveTicketWorkflowRuntimeState(meta, opts.fallbackStatus);
|
|
217
|
+
if (!action) return [`ticket_workflow_action_missing:${name}`];
|
|
218
|
+
const allowedActions = state.actions || [];
|
|
219
|
+
if (!allowedActions.includes(action)) {
|
|
220
|
+
return [`ticket_workflow_action_blocked:${name}:${action}`];
|
|
221
|
+
}
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function assertTicketWorkflowAction(meta: Record<string, any> = {}, opts: CliOpts = {}) {
|
|
226
|
+
const reasons = getTicketWorkflowActionReasons(meta, opts);
|
|
227
|
+
if (reasons.length === 0) return;
|
|
228
|
+
const { name, state } = resolveTicketWorkflowRuntimeState(meta, opts.fallbackStatus);
|
|
229
|
+
const allowedActions = (state.actions || []).join(", ") || "none";
|
|
230
|
+
const allowedTransitions = (state.transitions || []).join(", ") || "none";
|
|
231
|
+
const canDiscard = (state.actions || []).includes("discard");
|
|
232
|
+
const discardHint = canDiscard
|
|
233
|
+
? `\nTo abandon this ticket: ticket discard --id <id> --workspace <ws> --non-interactive`
|
|
234
|
+
: "";
|
|
235
|
+
throw new Error(
|
|
236
|
+
`[TICKET WORKFLOW BLOCKED] ${reasons.join(", ")}\n` +
|
|
237
|
+
`Current state: ${name} | Allowed actions: ${allowedActions} | Valid transitions: ${allowedTransitions}${discardHint}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function getTicketWorkflowTransitionReasons(meta: Record<string, any> = {}, opts: CliOpts = {}) {
|
|
242
|
+
const transition = resolveTicketWorkflowTransition(meta, opts);
|
|
243
|
+
const evidence = opts.evidence || {};
|
|
244
|
+
// #691: 증거 guard는 XState 머신이 검증한다. machineTransition이 guard 미충족 시
|
|
245
|
+
// missingEvidence를 산출하므로, 흩어진 검증 대신 머신을 단일 진실로 쓴다.
|
|
246
|
+
const verdict = machineTransition(transition.from, transition.to, evidence);
|
|
247
|
+
return {
|
|
248
|
+
transition,
|
|
249
|
+
reasons: verdict.missingEvidence || []
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function assertTicketWorkflowTransition(meta: Record<string, any> = {}, opts: CliOpts = {}) {
|
|
254
|
+
const result = getTicketWorkflowTransitionReasons(meta, opts);
|
|
255
|
+
if (result.reasons.length > 0) {
|
|
256
|
+
const fromState = TICKET_WORKFLOW.states[result.transition.from];
|
|
257
|
+
const validTransitions = (fromState?.transitions || []).join(", ") || "none";
|
|
258
|
+
throw new Error(
|
|
259
|
+
`[TICKET WORKFLOW BLOCKED] ${result.transition.from} -> ${result.transition.to}: ${result.reasons.join(", ")}\n` +
|
|
260
|
+
`Valid transitions from ${result.transition.from}: ${validTransitions}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return result.transition;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function readProjectPolicyText(cwd, policyRole, language, replacements: Record<string, any> = {}) {
|
|
267
|
+
if (!cwd || !policyRole) return "";
|
|
268
|
+
const localizedPolicyPath = makePath(cwd, DEUK_ROOT_DIR, "project-guardrails", `${policyRole}.${language}.md`);
|
|
269
|
+
const policyPath = existsSync(localizedPolicyPath)
|
|
270
|
+
? localizedPolicyPath
|
|
271
|
+
: makePath(cwd, DEUK_ROOT_DIR, "project-guardrails", `${policyRole}.md`);
|
|
272
|
+
if (!existsSync(policyPath)) return "";
|
|
273
|
+
let content = readFileSync(policyPath, "utf8");
|
|
274
|
+
const projectRulePath = makePath(cwd, DEUK_ROOT_DIR, "PROJECT_RULE.md");
|
|
275
|
+
const allReplacements = {
|
|
276
|
+
PROJECT_RULE_PATH: projectRulePath,
|
|
277
|
+
PROJECT_GUARDRAIL: policyRole,
|
|
278
|
+
PROJECT_POLICY_ROLE: policyRole,
|
|
279
|
+
...replacements
|
|
280
|
+
};
|
|
281
|
+
for (const [key, value] of Object.entries(allReplacements)) {
|
|
282
|
+
content = content.replaceAll(`{{${key}}}`, String(value ?? ""));
|
|
283
|
+
}
|
|
284
|
+
return content.trim();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function readLocalizedPackagePrompt(rootName, promptName, language, replacements: Record<string, any> = {}, missingLabel = "ticket workflow recipe") {
|
|
288
|
+
if (!promptName) return "";
|
|
289
|
+
const localizedPath = makePath(PACKAGE_ROOT, "docs", "cli-surfaces", rootName, `${promptName}.${language}.md`);
|
|
290
|
+
const fallbackPath = makePath(PACKAGE_ROOT, "docs", "cli-surfaces", rootName, `${promptName}.en.md`);
|
|
291
|
+
const promptPath = existsSync(localizedPath) ? localizedPath : fallbackPath;
|
|
292
|
+
if (!existsSync(promptPath)) {
|
|
293
|
+
throw new Error(`${missingLabel} not found: ${promptPath}`);
|
|
294
|
+
}
|
|
295
|
+
let content = readFileSync(promptPath, "utf8");
|
|
296
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
297
|
+
content = content.replaceAll(`{{${key}}}`, String(value ?? ""));
|
|
298
|
+
}
|
|
299
|
+
return content.trimEnd();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function buildTicketStateRecipe(kind, language, replacements: Record<string, any> = {}, context: Record<string, any> = {}) {
|
|
303
|
+
const workflowState = resolveTicketWorkflowState(kind);
|
|
304
|
+
const statePrompt = readLocalizedPackagePrompt(
|
|
305
|
+
"state",
|
|
306
|
+
workflowState.prompt,
|
|
307
|
+
language,
|
|
308
|
+
replacements,
|
|
309
|
+
"ticket workflow state recipe"
|
|
310
|
+
);
|
|
311
|
+
const projectPolicy = readProjectPolicyText(
|
|
312
|
+
context.cwd,
|
|
313
|
+
context.policyRole || workflowState.policyRole,
|
|
314
|
+
language,
|
|
315
|
+
replacements
|
|
316
|
+
);
|
|
317
|
+
const stateTemplate = readLocalizedPackagePrompt(
|
|
318
|
+
"state-template",
|
|
319
|
+
context.template || workflowState.template,
|
|
320
|
+
language,
|
|
321
|
+
replacements,
|
|
322
|
+
"ticket workflow state template"
|
|
323
|
+
);
|
|
324
|
+
const runtimeContext = renderTicketWorkflowRuntimeContext(kind, workflowState, context.runtimeContext);
|
|
325
|
+
return {
|
|
326
|
+
state_prompt: statePrompt,
|
|
327
|
+
state_template: stateTemplate,
|
|
328
|
+
runtime_context: runtimeContext,
|
|
329
|
+
policy_metadata: projectPolicy,
|
|
330
|
+
compact_recipe: [statePrompt, stateTemplate].filter(Boolean).join("\n\n---\n\n"),
|
|
331
|
+
full_recipe: [statePrompt, runtimeContext, projectPolicy, stateTemplate].filter(Boolean).join("\n\n---\n\n")
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function renderTicketWorkflowRuntimeContext(kind, workflowState, runtimeContext = null) {
|
|
336
|
+
if (!runtimeContext || Object.keys(runtimeContext).length === 0) return "";
|
|
337
|
+
const transitions = workflowState.transitions || [];
|
|
338
|
+
const actions = workflowState.actions || [];
|
|
339
|
+
const requiredEvidence = workflowState.requiredEvidence || {};
|
|
340
|
+
const runtimeLines = [
|
|
341
|
+
"## Ticket Workflow Runtime Context",
|
|
342
|
+
`- State: ${kind}`,
|
|
343
|
+
`- Ticket: ${runtimeContext.ticketId || ""}`,
|
|
344
|
+
`- Host Workspace: ${runtimeContext.hostWorkspaceLabel || runtimeContext.workspaceLabel || ""}`,
|
|
345
|
+
`- Workspace: ${runtimeContext.workspaceLabel || ""}`,
|
|
346
|
+
`- Path: ${runtimeContext.path || ""}`,
|
|
347
|
+
`- Phase/Status: ${runtimeContext.phase || ""}/${runtimeContext.status || ""}`,
|
|
348
|
+
`- Workflow Status: ${runtimeContext.status || ""}`,
|
|
349
|
+
`- Summary: ${runtimeContext.summary || ""}`,
|
|
350
|
+
`- Connection State: ${runtimeContext.connectionState || "unknown"}`,
|
|
351
|
+
`- Allowed actions: ${actions.length ? actions.join(", ") : "none"}`,
|
|
352
|
+
`- Allowed transitions: ${transitions.length ? transitions.join(", ") : "terminal"}`,
|
|
353
|
+
`- Required evidence: ${Object.entries(requiredEvidence).map(([target, evidence]) => `${target}=[${(evidence as string[]).join(", ")}]`).join("; ") || "none"}`,
|
|
354
|
+
`- Current blockers: ${(runtimeContext.reasons || []).length ? runtimeContext.reasons.join(", ") : "none"}`
|
|
355
|
+
];
|
|
356
|
+
if (runtimeContext.nextAction) runtimeLines.push(`- Next action: ${runtimeContext.nextAction}`);
|
|
357
|
+
return runtimeLines.join("\n");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeTicketWorkflowReplacements(replacements: Record<string, any> = {}) {
|
|
361
|
+
return Object.fromEntries(
|
|
362
|
+
Object.entries(replacements).map(([key, value]) => [key, value == null ? "" : value])
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function buildTicketDocMetaSurface(kind, opts: CliOpts = {}) {
|
|
367
|
+
const language = resolveGuideLanguage(
|
|
368
|
+
opts.docsLanguage,
|
|
369
|
+
opts.resolveLanguage || ((value) => value),
|
|
370
|
+
opts.normalizeDocsLanguage || ((value) => value)
|
|
371
|
+
);
|
|
372
|
+
const replacements = normalizeTicketWorkflowReplacements(opts.replacements);
|
|
373
|
+
const context = {
|
|
374
|
+
cwd: opts.cwd,
|
|
375
|
+
policyRole: opts.policyRole,
|
|
376
|
+
template: opts.template,
|
|
377
|
+
runtimeContext: opts.runtimeContext,
|
|
378
|
+
surfaceMode: opts.surfaceMode
|
|
379
|
+
};
|
|
380
|
+
const workflowState = resolveTicketWorkflowState(kind);
|
|
381
|
+
const recipe = buildTicketStateRecipe(kind, language, replacements, context);
|
|
382
|
+
const requiredEvidence = workflowState.requiredEvidence || {};
|
|
383
|
+
const runtimeContext = opts.runtimeContext || {};
|
|
384
|
+
const currentPhase = runtimeContext.phase || workflowState.phase || "";
|
|
385
|
+
const currentStatus = runtimeContext.status || workflowState.status || "";
|
|
386
|
+
// Dedupe: a slot (e.g. phase1Plan) can be required by more than one outgoing
|
|
387
|
+
// transition (phase2 and end), and flattening would list it twice.
|
|
388
|
+
const requiredSlots = [...new Set(Object.values(requiredEvidence).flat())];
|
|
389
|
+
const outputStatus = requiredSlots.length ? "action_requires_evidence" : "action_available";
|
|
390
|
+
return {
|
|
391
|
+
document_type: "agent_flow_surface",
|
|
392
|
+
document_subtype: "ticket_workflow_state",
|
|
393
|
+
contract_version: "documeta-0.1",
|
|
394
|
+
workflow: TICKET_WORKFLOW.name,
|
|
395
|
+
state: {
|
|
396
|
+
name: kind,
|
|
397
|
+
phase: currentPhase,
|
|
398
|
+
status: currentStatus,
|
|
399
|
+
actions: workflowState.actions || [],
|
|
400
|
+
transitions: workflowState.transitions || []
|
|
401
|
+
},
|
|
402
|
+
language,
|
|
403
|
+
recipe: {
|
|
404
|
+
prompt_id: workflowState.prompt || "",
|
|
405
|
+
template_id: context.template || workflowState.template || "",
|
|
406
|
+
text: recipe.compact_recipe
|
|
407
|
+
},
|
|
408
|
+
policy_metadata: {
|
|
409
|
+
role: context.policyRole || workflowState.policyRole || "",
|
|
410
|
+
text: recipe.policy_metadata
|
|
411
|
+
},
|
|
412
|
+
runtime_context: runtimeContext,
|
|
413
|
+
source_contract: {
|
|
414
|
+
required_slots: requiredSlots,
|
|
415
|
+
metadata_fields: ["ticket_id", "workspace", "phase", "status", "path"]
|
|
416
|
+
},
|
|
417
|
+
validation: {
|
|
418
|
+
status: outputStatus === "action_available" ? "PASS" : "READY",
|
|
419
|
+
errors: []
|
|
420
|
+
},
|
|
421
|
+
output_status: outputStatus,
|
|
422
|
+
debug: {
|
|
423
|
+
runtime_context_text: recipe.runtime_context,
|
|
424
|
+
full_recipe: recipe.full_recipe
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function renderTicketDocMetaSurface(kind, opts: CliOpts = {}) {
|
|
430
|
+
const docmeta = buildTicketDocMetaSurface(kind, opts);
|
|
431
|
+
if (opts.json) {
|
|
432
|
+
return JSON.stringify(docmeta, null, 2);
|
|
433
|
+
}
|
|
434
|
+
if (opts.surfaceMode === "full" || opts.verbose) {
|
|
435
|
+
return [
|
|
436
|
+
`DocMeta state: ${docmeta.state.name}`,
|
|
437
|
+
docmeta.debug.full_recipe
|
|
438
|
+
].filter(Boolean).join("\n\n---\n\n");
|
|
439
|
+
}
|
|
440
|
+
const runtime = docmeta.runtime_context || {};
|
|
441
|
+
const lines = [
|
|
442
|
+
`DocMeta state: ${docmeta.state.name}`,
|
|
443
|
+
runtime.ticketId ? `Ticket: ${runtime.ticketId}` : "",
|
|
444
|
+
runtime.summary ? `Summary: ${runtime.summary}` : "",
|
|
445
|
+
`Phase/Status: ${docmeta.state.phase || ""}/${docmeta.state.status || ""}`,
|
|
446
|
+
docmeta.source_contract.required_slots.length
|
|
447
|
+
? `Required slots: ${docmeta.source_contract.required_slots.join(", ")}`
|
|
448
|
+
: "Required slots: none",
|
|
449
|
+
runtime.nextAction ? `Next action: ${runtime.nextAction}` : "",
|
|
450
|
+
`Output: ${docmeta.output_status}`
|
|
451
|
+
];
|
|
452
|
+
return lines.filter(Boolean).join("\n");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function buildTicketFlowSurface(kind, opts: CliOpts = {}) {
|
|
456
|
+
const docmeta = buildTicketDocMetaSurface(kind, opts);
|
|
457
|
+
const flowLine = opts.flow
|
|
458
|
+
? formatTicketFlowLine(opts.flow.workspaceLabel, opts.flow.statusLabel, opts.flow.ticketId, opts.flow.absPath, opts.flow.invocationCwd || opts.cwd || "")
|
|
459
|
+
: "";
|
|
460
|
+
const surfaceText = renderTicketDocMetaSurface(kind, opts);
|
|
461
|
+
return {
|
|
462
|
+
...docmeta,
|
|
463
|
+
flowLine,
|
|
464
|
+
text: flowLine
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
export function renderTicketFlowSurface(kind, opts: CliOpts = {}) {
|
|
469
|
+
return buildTicketFlowSurface(kind, opts).text;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function normalizeTicketPhaseNumber(phase) {
|
|
473
|
+
const parsed = Number(phase);
|
|
474
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 1;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function sanitizeTicketWorkspaceLabel(workspaceLabel) {
|
|
478
|
+
const normalized = String(workspaceLabel || "").trim();
|
|
479
|
+
if (!normalized) return "";
|
|
480
|
+
if (/^(?:workspace|none|null|undefined)$/i.test(normalized)) return "";
|
|
481
|
+
return normalized;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export function formatTicketFlowLine(workspaceLabel, statusLabel, ticketId, absPath, cwd = "") {
|
|
485
|
+
const resolvedWorkspaceLabel = sanitizeTicketWorkspaceLabel(workspaceLabel);
|
|
486
|
+
if (!resolvedWorkspaceLabel) {
|
|
487
|
+
throw new Error("ticket flow surface requires a concrete workspace label");
|
|
488
|
+
}
|
|
489
|
+
const resolvedStatusLabel = String(statusLabel || "").trim();
|
|
490
|
+
if (!resolvedStatusLabel) {
|
|
491
|
+
throw new Error("ticket flow surface requires a concrete status label");
|
|
492
|
+
}
|
|
493
|
+
const absResolved = resolve(absPath);
|
|
494
|
+
// #646: tickets now live in the home store (~/.deuk-agent/tickets/{uuid}/),
|
|
495
|
+
// a different drive/root than the workspace cwd — relative(cwd, abs) leaks an
|
|
496
|
+
// unopenable path. Always emit a makePath-normalized absolute path so the
|
|
497
|
+
// review link opens from anywhere.
|
|
498
|
+
// #647: emit a POSIX absolute path WITHOUT the Windows drive letter
|
|
499
|
+
// ("C:/Users/..." → "/Users/..."). The Claude Code chat-panel click handler
|
|
500
|
+
// breaks on the drive-letter colon (anthropics/claude-code#53919), so a
|
|
501
|
+
// drive-prefixed link never opens. Stripping "C:" yields a slash-rooted path
|
|
502
|
+
// the chat UI opens, while the VS Code webview's vscode.open resolves it too.
|
|
503
|
+
const linkPath = toPosixPath(makePath(absResolved)).replace(/^[a-zA-Z]:/, "");
|
|
504
|
+
// Parse ticketId: "028-some-title-joy-deep" → num="028", slug="some-title-joy-deep"
|
|
505
|
+
const idMatch = String(ticketId).match(/^(\d+)-(.+)$/);
|
|
506
|
+
const num = idMatch ? idMatch[1] : "";
|
|
507
|
+
const rawSlug = idMatch ? idMatch[2] : ticketId;
|
|
508
|
+
const slug = rawSlug.length > 40 ? rawSlug.slice(0, 40) : rawSlug;
|
|
509
|
+
const label = num ? `${num} ${slug}` : slug;
|
|
510
|
+
return `📦 ${resolvedWorkspaceLabel} · ${resolvedStatusLabel} · [${label}](${linkPath})`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function getTicketGuide(kind, docsLanguage = "ko", resolveLanguage = (value) => value, normalizeDocsLanguage = (value) => value, context: Record<string, any> = {}) {
|
|
514
|
+
return renderTicketDocMetaSurface(kind, {
|
|
515
|
+
docsLanguage,
|
|
516
|
+
resolveLanguage,
|
|
517
|
+
normalizeDocsLanguage,
|
|
518
|
+
cwd: context.cwd,
|
|
519
|
+
policyRole: context.policyRole,
|
|
520
|
+
template: context.template
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function deriveTicketWorkflowStatus(meta: Record<string, any> = {}, fallbackStatus = "open") {
|
|
525
|
+
const rawStatus = String(meta.status || fallbackStatus || "open").toLowerCase();
|
|
526
|
+
const phase = normalizeTicketPhaseNumber(meta.phase || 1);
|
|
527
|
+
if (TERMINAL_TICKET_STATUSES.has(rawStatus)) {
|
|
528
|
+
return rawStatus;
|
|
529
|
+
}
|
|
530
|
+
if (phase >= 4) return "closed";
|
|
531
|
+
// phase>=2 no longer implies "active": status stays "open" through execution.
|
|
532
|
+
// Focus is tracked by activeTicketId, not by deriving active from phase.
|
|
533
|
+
if (phase >= 2) return rawStatus === "active" ? "open" : rawStatus;
|
|
534
|
+
return rawStatus || "open";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function isExecutionTicketStatus(status) {
|
|
538
|
+
return OPEN_TICKET_STATUSES.has(String(status || "open").toLowerCase());
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function inferTicketWorkflowStatus(meta: Record<string, any> = {}, isArchived = false) {
|
|
542
|
+
if (isArchived) return "archived";
|
|
543
|
+
return deriveTicketWorkflowStatus(meta, "open");
|
|
544
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Submodule Rules: [Module Name]
|
|
2
|
-
|
|
3
|
-
- **Path Root:** `[Path]`
|
|
4
|
-
- **Tech Stack:** `[Tech]`
|
|
5
|
-
|
|
6
|
-
## Architecture & Conventions
|
|
7
|
-
- [Add module-specific conventions here]
|
|
8
|
-
|
|
9
|
-
## Build & Test
|
|
10
|
-
- Build: `[Command]`
|
|
11
|
-
- Test: `[Command]`
|
|
1
|
+
# Submodule Rules: [Module Name]
|
|
2
|
+
|
|
3
|
+
- **Path Root:** `[Path]`
|
|
4
|
+
- **Tech Stack:** `[Tech]`
|
|
5
|
+
|
|
6
|
+
## Architecture & Conventions
|
|
7
|
+
- [Add module-specific conventions here]
|
|
8
|
+
|
|
9
|
+
## Build & Test
|
|
10
|
+
- Build: `[Command]`
|
|
11
|
+
- Test: `[Command]`
|
|
@@ -1,47 +1,46 @@
|
|
|
1
|
-
---
|
|
2
|
-
architecture_docs: ""
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Project Rules
|
|
6
|
-
|
|
7
|
-
## Architecture Boundaries
|
|
8
|
-
|
|
9
|
-
> [!NOTE] **To AI Agents**
|
|
10
|
-
> If `architecture_docs` is empty and no rules are defined below,
|
|
11
|
-
> this project has no architecture rules yet.
|
|
12
|
-
> → Ask user to define them, or analyze codebase and propose a draft.
|
|
13
|
-
> → Do NOT make assumptions about project architecture.
|
|
14
|
-
|
|
15
|
-
### Module Ownership
|
|
16
|
-
<!-- Define which modules exist and who owns them -->
|
|
17
|
-
| Module | Owner | Editable |
|
|
18
|
-
|--------|-------|----------|
|
|
19
|
-
| `src/` | Core source | Yes |
|
|
20
|
-
|
|
21
|
-
### Dependency Direction
|
|
22
|
-
<!-- Define allowed dependency directions between modules -->
|
|
23
|
-
|
|
24
|
-
## DC-CODEGEN: Generated File Mapping
|
|
25
|
-
|
|
26
|
-
> [!IMPORTANT] **REQUIRED**
|
|
27
|
-
> Agents use this table to decide which files are safe to edit.
|
|
28
|
-
> Files not listed here → agent MUST ask user before editing.
|
|
29
|
-
|
|
30
|
-
| Generated (DO NOT EDIT) | Source (edit here) | Build command |
|
|
31
|
-
|-------------------------|-------------------|---------------|
|
|
32
|
-
| `dist/` | `src/` | `npm run build` |
|
|
33
|
-
|
|
34
|
-
## DC-* Guards (project-specific)
|
|
35
|
-
|
|
36
|
-
| Guard | Condition → Action |
|
|
37
|
-
|-------|-------------------|
|
|
38
|
-
| DC-HALT | Infrastructure error → stop, no bypass, report to user. |
|
|
39
|
-
| DC-INFRA | Bootstrap/transport/DB/routing code → separate ticket + approval. |
|
|
40
|
-
|
|
41
|
-
## Build & Test
|
|
42
|
-
|
|
43
|
-
| Action | Command |
|
|
44
|
-
|--------|---------|
|
|
45
|
-
| Build | `npm run build` |
|
|
46
|
-
|
|
|
47
|
-
| Lint | `npx deuk-agent-flow lint:md` |
|
|
1
|
+
---
|
|
2
|
+
architecture_docs: ""
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Project Rules
|
|
6
|
+
|
|
7
|
+
## Architecture Boundaries
|
|
8
|
+
|
|
9
|
+
> [!NOTE] **To AI Agents**
|
|
10
|
+
> If `architecture_docs` is empty and no rules are defined below,
|
|
11
|
+
> this project has no architecture rules yet.
|
|
12
|
+
> → Ask user to define them, or analyze codebase and propose a draft.
|
|
13
|
+
> → Do NOT make assumptions about project architecture.
|
|
14
|
+
|
|
15
|
+
### Module Ownership
|
|
16
|
+
<!-- Define which modules exist and who owns them -->
|
|
17
|
+
| Module | Owner | Editable |
|
|
18
|
+
|--------|-------|----------|
|
|
19
|
+
| `src/` | Core source | Yes |
|
|
20
|
+
|
|
21
|
+
### Dependency Direction
|
|
22
|
+
<!-- Define allowed dependency directions between modules -->
|
|
23
|
+
|
|
24
|
+
## DC-CODEGEN: Generated File Mapping
|
|
25
|
+
|
|
26
|
+
> [!IMPORTANT] **REQUIRED**
|
|
27
|
+
> Agents use this table to decide which files are safe to edit.
|
|
28
|
+
> Files not listed here → agent MUST ask user before editing.
|
|
29
|
+
|
|
30
|
+
| Generated (DO NOT EDIT) | Source (edit here) | Build command |
|
|
31
|
+
|-------------------------|-------------------|---------------|
|
|
32
|
+
| `dist/` | `src/` | `npm run build` |
|
|
33
|
+
|
|
34
|
+
## DC-* Guards (project-specific)
|
|
35
|
+
|
|
36
|
+
| Guard | Condition → Action |
|
|
37
|
+
|-------|-------------------|
|
|
38
|
+
| DC-HALT | Infrastructure error → stop, no bypass, report to user. |
|
|
39
|
+
| DC-INFRA | Bootstrap/transport/DB/routing code → separate ticket + approval. |
|
|
40
|
+
|
|
41
|
+
## Build & Test
|
|
42
|
+
|
|
43
|
+
| Action | Command |
|
|
44
|
+
|--------|---------|
|
|
45
|
+
| Build | `npm run build` |
|
|
46
|
+
| Lint | `npx deuk-agent-flow lint:md` |
|