@aipper/aiws-spec 0.0.29 → 0.0.31
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/docs/workflow-delegation-contracts.json +40 -1
- package/docs/workflow-delegation-contracts.md +10 -0
- package/docs/workflow-delegation-contracts.schema.json +57 -0
- package/docs/workflow-governance-rules.md +1 -1
- package/docs/workflow-review-gates.json +6 -0
- package/docs/workflow-review-gates.md +14 -5
- package/docs/workflow-review-gates.schema.json +4 -0
- package/docs/workflow-router-rules.json +46 -14
- package/docs/workflow-router-rules.md +19 -16
- package/docs/workflow-router-rules.schema.json +4 -0
- package/docs/workflow-stage-contracts.json +22 -2
- package/docs/workflow-stage-contracts.md +15 -13
- package/docs/workflow-stage-contracts.schema.json +12 -1
- package/package.json +1 -1
- package/templates/workspace/.agents/skills/p-aiws-change-archive/SKILL.md +1 -1
- package/templates/workspace/.agents/skills/p-aiws-change-finish/SKILL.md +1 -1
- package/templates/workspace/.agents/skills/p-aiws-change-new/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/p-aiws-change-start/SKILL.md +1 -1
- package/templates/workspace/.agents/skills/p-aiws-change-sync/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/p-aiws-change-validate/SKILL.md +1 -1
- package/templates/workspace/.agents/skills/p-aiws-validate/SKILL.md +1 -1
- package/templates/workspace/.agents/skills/p-tasks-plan/SKILL.md +7 -7
- package/templates/workspace/.agents/skills/using-aiws/SKILL.md +11 -7
- package/templates/workspace/.agents/skills/ws-analyze/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/ws-bugfix/SKILL.md +8 -8
- package/templates/workspace/.agents/skills/ws-commit/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-delegate/SKILL.md +4 -4
- package/templates/workspace/.agents/skills/ws-deliver/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-dev/SKILL.md +32 -2
- package/templates/workspace/.agents/skills/ws-dev-lite/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/ws-finish/SKILL.md +9 -5
- package/templates/workspace/.agents/skills/ws-frontend-design/SKILL.md +1 -1
- package/templates/workspace/.agents/skills/ws-handoff/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-migrate/SKILL.md +18 -0
- package/templates/workspace/.agents/skills/ws-plan/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-plan-verify/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-pull/SKILL.md +18 -0
- package/templates/workspace/.agents/skills/ws-push/SKILL.md +18 -0
- package/templates/workspace/.agents/skills/ws-quality-review/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-review/SKILL.md +8 -8
- package/templates/workspace/.agents/skills/ws-spec-review/SKILL.md +5 -5
- package/templates/workspace/.agents/skills/ws-submodule-setup/SKILL.md +18 -0
- package/templates/workspace/.agents/skills/ws-verify-before-complete/SKILL.md +5 -5
- package/templates/workspace/{changes → .aiws/changes}/README.md +13 -13
- package/templates/workspace/{changes → .aiws/changes}/templates/proposal.md +12 -12
- package/templates/workspace/{changes → .aiws/changes}/templates/tasks.md +8 -8
- package/templates/workspace/{issues → .aiws/issues}/problem-issues.csv +1 -1
- package/templates/workspace/.aiws/journal/.gitkeep +1 -0
- package/templates/workspace/{requirements → .aiws/requirements}/requirements-issues.csv +1 -1
- package/templates/workspace/.aiws/tmp/.gitkeep +0 -0
- package/templates/workspace/{tools → .aiws/tools}/requirements_contract.py +2 -2
- package/templates/workspace/{tools → .aiws/tools}/requirements_contract_sync.py +2 -2
- package/templates/workspace/{tools → .aiws/tools}/requirements_flow_gen.py +1 -1
- package/templates/workspace/{tools → .aiws/tools}/server_test_runner.py +10 -10
- package/templates/workspace/{tools → .aiws/tools}/ws_change_check.py +23 -23
- package/templates/workspace/{tools → .aiws/tools}/ws_resolve_sub_target.sh +2 -2
- package/templates/workspace/{tools → .aiws/tools}/ws_tasks_plan.py +3 -3
- package/templates/workspace/.claude/skills/ws-commit/SKILL.md +5 -5
- package/templates/workspace/.claude/skills/ws-deliver/SKILL.md +5 -5
- package/templates/workspace/.claude/skills/ws-finish/SKILL.md +9 -5
- package/templates/workspace/.claude/skills/ws-handoff/SKILL.md +5 -5
- package/templates/workspace/.claude/skills/ws-migrate/SKILL.md +18 -0
- package/templates/workspace/.claude/skills/ws-plan-verify/SKILL.md +5 -5
- package/templates/workspace/.claude/skills/ws-pull/SKILL.md +18 -0
- package/templates/workspace/.claude/skills/ws-push/SKILL.md +18 -0
- package/templates/workspace/.claude/skills/ws-submodule-setup/SKILL.md +18 -0
- package/templates/workspace/.claude/skills/ws-verify-before-complete/SKILL.md +5 -5
- package/templates/workspace/.githooks/pre-commit +13 -0
- package/templates/workspace/.githooks/pre-push +7 -0
- package/templates/workspace/.opencode/commands/ws-finish.md +66 -1
- package/templates/workspace/.opencode/helpers/approval-whitelist-check.sh +1 -1
- package/templates/workspace/.opencode/helpers/approval-whitelist-run.sh +1 -1
- package/templates/workspace/.opencode/helpers/approval-whitelist-watchdog.sh +1 -1
- package/templates/workspace/.opencode/helpers/tmux-swarm-rescue.sh +1 -1
- package/templates/workspace/.opencode/helpers/tmux-swarm-scan.sh +1 -1
- package/templates/workspace/.opencode/lib/aiws-context.js +953 -0
- package/templates/workspace/.opencode/plugins/aiws-inject-context.js +201 -0
- package/templates/workspace/.opencode/plugins/aiws-session-start.js +86 -0
- package/templates/workspace/.opencode/plugins/aiws-workflow-state.js +116 -0
- package/templates/workspace/.opencode/scripts/ws-plan-gen-submodule-targets.sh +27 -0
- package/templates/workspace/.opencode/scripts/ws-plan-setup-worktree.sh +31 -0
- package/templates/workspace/.opencode/skills/p-aiws-change-archive/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/p-aiws-change-finish/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/p-aiws-change-new/SKILL.md +2 -2
- package/templates/workspace/.opencode/skills/p-aiws-change-start/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/p-aiws-change-sync/SKILL.md +2 -2
- package/templates/workspace/.opencode/skills/p-aiws-change-validate/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/p-aiws-validate/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/p-tasks-plan/SKILL.md +7 -7
- package/templates/workspace/.opencode/skills/using-aiws/SKILL.md +40 -3
- package/templates/workspace/.opencode/skills/ws-analyze/SKILL.md +2 -2
- package/templates/workspace/.opencode/skills/ws-autonomy/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/ws-bugfix/SKILL.md +7 -7
- package/templates/workspace/.opencode/skills/ws-commit/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-delegate/SKILL.md +26 -4
- package/templates/workspace/.opencode/skills/ws-deliver/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-dev/SKILL.md +54 -6
- package/templates/workspace/.opencode/skills/ws-dev-lite/SKILL.md +6 -3
- package/templates/workspace/.opencode/skills/ws-finish/SKILL.md +9 -5
- package/templates/workspace/.opencode/skills/ws-frontend-design/SKILL.md +1 -1
- package/templates/workspace/.opencode/skills/ws-handoff/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-intake/SKILL.md +32 -2
- package/templates/workspace/.opencode/skills/ws-migrate/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-plan/SKILL.md +16 -58
- package/templates/workspace/.opencode/skills/ws-plan-verify/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-pull/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-push/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-quality-review/SKILL.md +11 -6
- package/templates/workspace/.opencode/skills/ws-review/SKILL.md +33 -13
- package/templates/workspace/.opencode/skills/ws-spec-review/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-submodule-setup/SKILL.md +5 -5
- package/templates/workspace/.opencode/skills/ws-verify-before-complete/SKILL.md +36 -5
- package/templates/workspace/AGENTS.md +32 -87
- package/templates/workspace/AI_PROJECT.md +4 -4
- package/templates/workspace/AI_WORKSPACE.md +1 -1
- package/templates/workspace/REQUIREMENTS.md +1 -1
- package/templates/workspace/gitignore +1 -1
- package/templates/workspace/manifest.json +96 -334
- /package/templates/workspace/{changes → .aiws/changes}/templates/design.md +0 -0
- /package/templates/workspace/{memory-bank → .aiws/memory-bank}/README.md +0 -0
- /package/templates/workspace/{memory-bank → .aiws/memory-bank}/architecture.md +0 -0
- /package/templates/workspace/{memory-bank → .aiws/memory-bank}/implementation-plan.md +0 -0
- /package/templates/workspace/{memory-bank → .aiws/memory-bank}/progress.md +0 -0
- /package/templates/workspace/{memory-bank → .aiws/memory-bank}/tech-stack.md +0 -0
- /package/templates/workspace/{requirements → .aiws/requirements}/CHANGELOG.md +0 -0
- /package/templates/workspace/{secrets → .aiws/secrets}/test-accounts.example.json +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aiws Context Injection Plugin
|
|
3
|
+
*
|
|
4
|
+
* Injects aiws context JSONL when task() is called with sub-agent types.
|
|
5
|
+
* Hook: tool.execute.before — intercepts task() calls, reads <role>-context.jsonl
|
|
6
|
+
* from changes/<id>/analysis/, and injects into the agent prompt.
|
|
7
|
+
*
|
|
8
|
+
* Reference: trellis/packages/cli/src/templates/opencode/plugins/inject-subagent-context.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync } from "fs"
|
|
12
|
+
import { join } from "path"
|
|
13
|
+
import { AiwsContext, AiwsSessionJournal, debugLog } from "../lib/aiws-context.js"
|
|
14
|
+
|
|
15
|
+
// Role detection — check args.subagent_type first, then fall back to prompt marker
|
|
16
|
+
// Prompt should include: "role: <role>" as a line in the prompt
|
|
17
|
+
const ROLE_MARKER_RE = /^\s*role\s*[:=]\s*(\w+)\s*$/im
|
|
18
|
+
|
|
19
|
+
function detectRole(args, originalPrompt) {
|
|
20
|
+
// Priority 1: args.subagent_type (set by task() caller)
|
|
21
|
+
if (args?.subagent_type) {
|
|
22
|
+
const t = String(args.subagent_type).toLowerCase().replace(/^(?:trellis|aiws)-/, "")
|
|
23
|
+
if (["worker", "explorer", "reviewer", "planner"].includes(t)) return t
|
|
24
|
+
}
|
|
25
|
+
// Priority 2: "role:" marker in prompt body
|
|
26
|
+
const roleMatch = originalPrompt?.match(ROLE_MARKER_RE)
|
|
27
|
+
if (roleMatch) return roleMatch[1].toLowerCase()
|
|
28
|
+
// Priority 3: "subagent_type" or "agent_type" in prompt body
|
|
29
|
+
const typeMatch = originalPrompt?.match(/\b(?:subagent_type|agent_type)\s*[:=]\s*(\w+)\s*/im)
|
|
30
|
+
if (typeMatch) return typeMatch[1].toLowerCase().replace(/^trellis-/, "")
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const FIRST_REPLY_INSTRUCTION = `<aiws-context-instruction>
|
|
35
|
+
After reading the JSONL context files, confirm by listing the files you read and their key points (≤3 per file).
|
|
36
|
+
|
|
37
|
+
**Context file path:** changes/<change-id>/analysis/<role>-context.jsonl
|
|
38
|
+
|
|
39
|
+
**Execution order:**
|
|
40
|
+
1. Read the JSONL file with the Read tool
|
|
41
|
+
2. Follow priority order: high → medium → low
|
|
42
|
+
3. If sections defined, only read those line ranges for files > 200 lines
|
|
43
|
+
4. If any file is inaccessible, flag it but continue with what's available
|
|
44
|
+
|
|
45
|
+
**Your first response** must begin with:
|
|
46
|
+
\`\`\`
|
|
47
|
+
Read context files: [list of files read]
|
|
48
|
+
Key points: [brief summary per file]
|
|
49
|
+
Status: READY | NEEDS_CONTEXT
|
|
50
|
+
\`\`\`
|
|
51
|
+
</aiws-context-instruction>`
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the active change ID by scanning changes/ directory.
|
|
55
|
+
*/
|
|
56
|
+
function resolveActiveChangeId(ctx) {
|
|
57
|
+
const active = ctx.getActiveChange()
|
|
58
|
+
return active ? active.id : null
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Find JSONL context file for a given role in the active change.
|
|
63
|
+
*/
|
|
64
|
+
function resolveRoleJsonl(ctx, changeId, role) {
|
|
65
|
+
if (!changeId || !role) return null
|
|
66
|
+
const jsonlPath = join(ctx.directory, "changes", changeId, "analysis", `${role}-context.jsonl`)
|
|
67
|
+
return existsSync(jsonlPath) ? jsonlPath : null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Build fallback context when no curated JSONL exists (G7).
|
|
72
|
+
* Injects at minimum: change ID, phase, spec index.
|
|
73
|
+
*/
|
|
74
|
+
function buildFallbackContext(ctx, changeId, role) {
|
|
75
|
+
const parts = []
|
|
76
|
+
parts.push(`<aiws-context-fallback>
|
|
77
|
+
No curated context JSONL for role "${role}" in change "${changeId}".
|
|
78
|
+
Using project context as minimal scaffold.
|
|
79
|
+
</aiws-context-fallback>`)
|
|
80
|
+
|
|
81
|
+
const changeDetail = ctx.getChangeDetail(changeId)
|
|
82
|
+
if (changeDetail) {
|
|
83
|
+
const lines = [
|
|
84
|
+
`Change: ${changeDetail.id}`,
|
|
85
|
+
`Phase: ${changeDetail.phase}`,
|
|
86
|
+
]
|
|
87
|
+
if (changeDetail.planFiles.length > 0) {
|
|
88
|
+
lines.push(`Plans: ${changeDetail.planFiles.join(", ")}`)
|
|
89
|
+
}
|
|
90
|
+
parts.push(`<change-context>\n${lines.join("\n")}\n</change-context>`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const specIndex = ctx.getSpecIndex()
|
|
94
|
+
if (specIndex) {
|
|
95
|
+
parts.push(`<spec-index>\n${specIndex.trimEnd()}\n</spec-index>`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
parts.push(`<aiws-context-instruction>
|
|
99
|
+
No JSONL context file was found for your role.
|
|
100
|
+
Review the change context and spec index above for relevant constraints.
|
|
101
|
+
Focus on the spec documents that apply to your task.
|
|
102
|
+
If you need more context, use the Glob and Grep tools to discover the codebase.
|
|
103
|
+
</aiws-context-instruction>`)
|
|
104
|
+
|
|
105
|
+
return parts.join("\n\n")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Write journal entry for a fallback context injection.
|
|
110
|
+
* Extracted to avoid repeating the sessionKey/journal creation in both fallback paths.
|
|
111
|
+
*/
|
|
112
|
+
function _writeFallbackJournal(ctx, input, role, changeId, reason) {
|
|
113
|
+
const sessionKey = ctx.getSessionKey({ sessionID: input?.session_id || "" })
|
|
114
|
+
if (sessionKey) {
|
|
115
|
+
try {
|
|
116
|
+
const journal = new AiwsSessionJournal(ctx.directory)
|
|
117
|
+
journal.writeDelegation(sessionKey, role, changeId, {
|
|
118
|
+
contextFiles: 0,
|
|
119
|
+
hasFallback: true,
|
|
120
|
+
reason,
|
|
121
|
+
})
|
|
122
|
+
} catch {
|
|
123
|
+
// journal write failure should never break plugin flow
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default async ({ directory }) => {
|
|
129
|
+
const ctx = new AiwsContext(directory)
|
|
130
|
+
debugLog("inject", "Plugin loaded, directory:", directory)
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
"tool.execute.before": async (input, output) => {
|
|
134
|
+
try {
|
|
135
|
+
if (process.env.AIWS_HOOKS === "0" || process.env.AIWS_DISABLE_HOOKS === "1") {
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const toolName = input?.tool?.toLowerCase()
|
|
140
|
+
if (toolName !== "task") return
|
|
141
|
+
|
|
142
|
+
const args = output?.args
|
|
143
|
+
if (!args) return
|
|
144
|
+
|
|
145
|
+
const originalPrompt = args.prompt || ""
|
|
146
|
+
if (!originalPrompt) return
|
|
147
|
+
|
|
148
|
+
// Extract role using detectRole (checks subagent_type first, then prompt markers)
|
|
149
|
+
const role = detectRole(args, originalPrompt) || "worker"
|
|
150
|
+
debugLog("inject", "Task tool called, role:", role)
|
|
151
|
+
|
|
152
|
+
// Resolve active change
|
|
153
|
+
const changeId = resolveActiveChangeId(ctx)
|
|
154
|
+
if (!changeId) {
|
|
155
|
+
debugLog("inject", "No active change found, skipping")
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Find context JSONL for this role
|
|
160
|
+
const jsonlPath = resolveRoleJsonl(ctx, changeId, role)
|
|
161
|
+
if (!jsonlPath) {
|
|
162
|
+
debugLog("inject", `No context JSONL for role "${role}" in change "${changeId}", injecting fallback`)
|
|
163
|
+
_writeFallbackJournal(ctx, input, role, changeId, "no_jsonl_file")
|
|
164
|
+
const fallbackCtx = buildFallbackContext(ctx, changeId, role)
|
|
165
|
+
args.prompt = `${fallbackCtx}\n\n---\n\n${originalPrompt}`
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Read JSONL and build context
|
|
170
|
+
const entries = ctx.readJsonlWithFiles(jsonlPath)
|
|
171
|
+
if (entries.length === 0) {
|
|
172
|
+
debugLog("inject", `Empty context JSONL for role "${role}" in change "${changeId}", injecting fallback`)
|
|
173
|
+
_writeFallbackJournal(ctx, input, role, changeId, "empty_jsonl")
|
|
174
|
+
const fallbackCtx = buildFallbackContext(ctx, changeId, role)
|
|
175
|
+
args.prompt = `${fallbackCtx}\n\n---\n\n${originalPrompt}`
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const contextBlock = ctx.buildContextBlock(entries)
|
|
180
|
+
debugLog("inject", `Injected context for role "${role}", entries: ${entries.length}`)
|
|
181
|
+
|
|
182
|
+
// Record delegation event to journal
|
|
183
|
+
const sessionKey = ctx.getSessionKey({ sessionID: input?.session_id || "" })
|
|
184
|
+
if (sessionKey) {
|
|
185
|
+
const journal = new AiwsSessionJournal(ctx.directory)
|
|
186
|
+
journal.writeDelegation(sessionKey, role, changeId, {
|
|
187
|
+
contextFiles: entries.length,
|
|
188
|
+
originalPromptSnippet: originalPrompt.slice(0, 120),
|
|
189
|
+
hasFallback: false,
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Prepend context + instruction to the prompt
|
|
194
|
+
args.prompt = `${contextBlock}\n\n${FIRST_REPLY_INSTRUCTION}\n\n---\n\n${originalPrompt}`
|
|
195
|
+
|
|
196
|
+
} catch (error) {
|
|
197
|
+
debugLog("inject", "Error in tool.execute.before:", error.message, error.stack)
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aiws Session Start Plugin
|
|
3
|
+
*
|
|
4
|
+
* Injects aiws context (spec index, change status, handoff) when user
|
|
5
|
+
* sends the first message in a new session.
|
|
6
|
+
*
|
|
7
|
+
* Hook: chat.message — modifies the output to prepend context.
|
|
8
|
+
*
|
|
9
|
+
* Reference: trellis/packages/cli/src/templates/opencode/plugins/session-start.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { AiwsContext, buildSessionContext, contextCollector, debugLog } from "../lib/aiws-context.js"
|
|
13
|
+
|
|
14
|
+
export default async ({ directory, client }) => {
|
|
15
|
+
const ctx = new AiwsContext(directory)
|
|
16
|
+
debugLog("session", "Plugin loaded, directory:", directory)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
event: ({ event }) => {
|
|
20
|
+
try {
|
|
21
|
+
if (event?.type === "session.compacted" && event?.properties?.sessionID) {
|
|
22
|
+
contextCollector.clear(event.properties.sessionID)
|
|
23
|
+
debugLog("session", "Cleared processed flag after compaction for session:", event.properties.sessionID)
|
|
24
|
+
}
|
|
25
|
+
} catch (error) {
|
|
26
|
+
debugLog("session", "Error in event hook:", error instanceof Error ? error.message : String(error))
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
"chat.message": async (input, output) => {
|
|
31
|
+
try {
|
|
32
|
+
const sessionKey = ctx.getSessionKey(input)
|
|
33
|
+
if (!sessionKey) {
|
|
34
|
+
debugLog("session", "No session key available, skipping")
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const agent = input.agent || ""
|
|
39
|
+
if (agent && (agent.startsWith("trellis-") || agent.startsWith("aiws-"))) {
|
|
40
|
+
debugLog("session", "Skipping sub-agent turn:", agent)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (process.env.AIWS_HOOKS === "0" || process.env.AIWS_DISABLE_HOOKS === "1") {
|
|
45
|
+
debugLog("session", "Skipping — AIWS_HOOKS disabled")
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (process.env.OPENCODE_NON_INTERACTIVE === "1") {
|
|
50
|
+
debugLog("session", "Skipping — non-interactive mode")
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (contextCollector.isProcessed(sessionKey)) {
|
|
55
|
+
debugLog("session", "Skipping — session already processed:", sessionKey)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const context = buildSessionContext(ctx, input)
|
|
60
|
+
debugLog("session", "Built context, length:", context.length)
|
|
61
|
+
|
|
62
|
+
if (!context) {
|
|
63
|
+
debugLog("session", "Empty context, nothing to inject")
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const parts = output?.parts || []
|
|
68
|
+
const textPartIndex = parts.findIndex(p => p.type === "text" && p.text !== undefined)
|
|
69
|
+
|
|
70
|
+
if (textPartIndex !== -1) {
|
|
71
|
+
const originalText = parts[textPartIndex].text || ""
|
|
72
|
+
parts[textPartIndex].text = `${context}\n\n---\n\n${originalText}`
|
|
73
|
+
debugLog("session", "Injected context into chat.message text part, length:", context.length)
|
|
74
|
+
} else {
|
|
75
|
+
const injectedPart = { type: "text", text: context }
|
|
76
|
+
parts.unshift(injectedPart)
|
|
77
|
+
debugLog("session", "Prepended new text part with context, length:", context.length)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
contextCollector.markProcessed(sessionKey)
|
|
81
|
+
} catch (error) {
|
|
82
|
+
debugLog("session", "Error in chat.message:", error.message, error.stack)
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aiws Workflow State Injection Plugin
|
|
3
|
+
*
|
|
4
|
+
* Per-turn chat.message breadcrumb showing active change + current phase.
|
|
5
|
+
* Unlike session-start, this fires on EVERY user message so long
|
|
6
|
+
* conversations don't lose context of what's being worked on.
|
|
7
|
+
*
|
|
8
|
+
* Reference: trellis/packages/cli/src/templates/opencode/plugins/inject-workflow-state.js
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { AiwsContext, AiwsSessionJournal, debugLog } from "../lib/aiws-context.js"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Build the workflow-state breadcrumb block.
|
|
15
|
+
*/
|
|
16
|
+
function buildBreadcrumb(change, phase) {
|
|
17
|
+
const parts = []
|
|
18
|
+
parts.push("<workflow-state>")
|
|
19
|
+
|
|
20
|
+
if (change) {
|
|
21
|
+
parts.push(`Active Change: ${change.id}`)
|
|
22
|
+
parts.push(`Change Dir: changes/${change.id}/`)
|
|
23
|
+
|
|
24
|
+
if (change.planFiles.length > 0) {
|
|
25
|
+
parts.push(`Plans: ${change.planFiles.slice(0, 3).join(", ")}`)
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
parts.push("Active Change: none")
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (phase) {
|
|
32
|
+
parts.push(`Phase: ${phase} (${AiwsContext.PHASES.indexOf(phase) + 1}/${AiwsContext.PHASES.length})`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Phase-specific next actions — aligned with AiwsContext.PHASES state machine
|
|
36
|
+
// Subagent-first policy: ready-for-dev defaults to aiws-worker; in-progress defaults to aiws-reviewer
|
|
37
|
+
const nextActions = {
|
|
38
|
+
intake: "Next: ws-plan to create a change and execution plan",
|
|
39
|
+
planning: "Next: ws-plan-verify to review the plan, or ws-dev to begin implementation",
|
|
40
|
+
"ready-for-dev": "Next: dispatch aiws-worker (subagent-first); main session coordinates, does not write code directly",
|
|
41
|
+
"in-progress": "Next: dispatch aiws-reviewer + ws-review to audit changes",
|
|
42
|
+
review: "Next: ws-commit to validate and commit",
|
|
43
|
+
finished: "Next: ws-finish to finalize and archive the change",
|
|
44
|
+
}
|
|
45
|
+
if (phase && nextActions[phase]) {
|
|
46
|
+
parts.push(nextActions[phase])
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!change && !phase) {
|
|
50
|
+
parts.push("No active work detected. Start with ws-preflight or ws-plan.")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Available phases reference — aligned with AiwsContext.PHASES
|
|
54
|
+
parts.push(`Phases: ${AiwsContext.PHASES.join(" → ")}`)
|
|
55
|
+
|
|
56
|
+
parts.push("</workflow-state>")
|
|
57
|
+
return parts.join("\n")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default async ({ directory }) => {
|
|
61
|
+
const ctx = new AiwsContext(directory)
|
|
62
|
+
debugLog("workflow-state", "Plugin loaded, directory:", directory)
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
"chat.message": async (input, output) => {
|
|
66
|
+
try {
|
|
67
|
+
// Skip sub-agent turns
|
|
68
|
+
const agent = input?.agent || ""
|
|
69
|
+
if (agent.startsWith("trellis-") || agent.startsWith("aiws-")) {
|
|
70
|
+
debugLog("workflow-state", "Skipping subagent turn:", agent)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (process.env.AIWS_HOOKS === "0" || process.env.AIWS_DISABLE_HOOKS === "1") {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (process.env.OPENCODE_NON_INTERACTIVE === "1") {
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!ctx.isAiwsProject()) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const change = ctx.getActiveChange()
|
|
87
|
+
const phase = change ? change.phase : null
|
|
88
|
+
const breadcrumb = buildBreadcrumb(change, phase)
|
|
89
|
+
|
|
90
|
+
// Inject into the output text part (no dedup — show every turn)
|
|
91
|
+
const parts = output?.parts || []
|
|
92
|
+
const textIdx = parts.findIndex(p => p.type === "text" && p.text !== undefined)
|
|
93
|
+
if (textIdx !== -1) {
|
|
94
|
+
parts[textIdx].text = `${breadcrumb}\n\n${parts[textIdx].text}`
|
|
95
|
+
} else {
|
|
96
|
+
parts.unshift({ type: "text", text: breadcrumb })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
debugLog("workflow-state", "Injected breadcrumb:", change?.id || "none", "phase:", phase || "none")
|
|
100
|
+
|
|
101
|
+
// Record breadcrumb event to journal (non-blocking)
|
|
102
|
+
try {
|
|
103
|
+
const sessionKey = ctx.getSessionKey({ sessionID: input?.session_id || "" })
|
|
104
|
+
if (sessionKey) {
|
|
105
|
+
const journal = new AiwsSessionJournal(ctx.directory)
|
|
106
|
+
journal.writeBreadcrumb(sessionKey, phase || "unknown", change?.id || null)
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// Journal write failure should never break the plugin
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
debugLog("workflow-state", "Error in chat.message:", error instanceof Error ? error.message : String(error))
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ws-plan: generate submodules.targets for the current change
|
|
3
|
+
# Usage: bash ws-plan-gen-submodule-targets.sh <change-id>
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
change_id="${1:?missing change_id}"
|
|
7
|
+
targets=".aiws/changes/${change_id}/submodules.targets"
|
|
8
|
+
|
|
9
|
+
mkdir -p ".aiws/changes/${change_id}"
|
|
10
|
+
|
|
11
|
+
if [[ -f "${targets}" ]]; then
|
|
12
|
+
bak="${targets}.bak.$(date -u +%Y%m%d-%H%M%SZ)"
|
|
13
|
+
cp "${targets}" "${bak}"
|
|
14
|
+
echo "info: backup: ${bak}"
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
: > "${targets}"
|
|
18
|
+
echo "# path<TAB>target_branch<TAB>remote(optional, default=origin)" >> "${targets}"
|
|
19
|
+
|
|
20
|
+
while read -r key sub_path; do
|
|
21
|
+
name="${key#submodule.}"; name="${name%.path}"
|
|
22
|
+
b="$(git config --file .gitmodules --get "submodule.${name}.branch" 2>/dev/null || true)"
|
|
23
|
+
[[ "${b:-}" == "." ]] && b="$(git branch --show-current)" # '.' means "follow superproject branch"
|
|
24
|
+
printf "%s\t%s\t%s\n" "${sub_path}" "${b:-<fill-me>}" "origin" >> "${targets}"
|
|
25
|
+
done < <(git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null || true)
|
|
26
|
+
|
|
27
|
+
echo "ok: wrote ${targets}"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ws-plan: create change context with worktree/submodule support
|
|
3
|
+
# Usage: bash ws-plan-setup-worktree.sh <change-id>
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
change_id="${1:?missing change_id}"
|
|
7
|
+
|
|
8
|
+
# Guard: refuse to create context on dirty working tree
|
|
9
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
10
|
+
echo "error: working tree dirty before ws-plan creates change context"
|
|
11
|
+
echo "hint: commit/stash first, or continue inside an existing change/<change-id> context"
|
|
12
|
+
exit 2
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
has_commits=0
|
|
16
|
+
git rev-parse --verify HEAD >/dev/null 2>&1 && has_commits=1
|
|
17
|
+
|
|
18
|
+
has_submodules=0
|
|
19
|
+
if [[ -f .gitmodules ]] && git config --file .gitmodules --get-regexp '^submodule\..*\.path$' >/dev/null 2>&1; then
|
|
20
|
+
has_submodules=1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if [[ "${has_commits}" -eq 1 ]]; then
|
|
24
|
+
if [[ "${has_submodules}" -eq 1 ]]; then
|
|
25
|
+
aiws change start "${change_id}" --hooks --worktree --submodules
|
|
26
|
+
else
|
|
27
|
+
aiws change start "${change_id}" --hooks --worktree
|
|
28
|
+
fi
|
|
29
|
+
else
|
|
30
|
+
aiws change start "${change_id}" --hooks --no-switch
|
|
31
|
+
fi
|
|
@@ -20,5 +20,5 @@ fi
|
|
|
20
20
|
|
|
21
21
|
说明:
|
|
22
22
|
- 默认等价于:`git merge --ff-only change/<change-id>`
|
|
23
|
-
- 若你当前就在 `change/<change-id>` 分支上,`finish` 会尝试读取
|
|
23
|
+
- 若你当前就在 `change/<change-id>` 分支上,`finish` 会尝试读取 `.aiws/changes/<change-id>/.ws-change.json` 的 `base_branch` 作为目标分支
|
|
24
24
|
- 若无法 fast-forward:先在 change 分支(或对应 worktree)里 `git rebase <target-branch>`,再重试 `aiws change finish`
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: p-aiws-change-new
|
|
3
|
-
description: 私有:创建 changes/<change-id> 工件
|
|
3
|
+
description: 私有:创建 .aiws/changes/<change-id> 工件
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
目标:
|
|
7
|
-
- 创建
|
|
7
|
+
- 创建 `.aiws/changes/<change-id>/` 工件目录与基础文件(proposal/tasks/可选 design)
|
|
8
8
|
|
|
9
9
|
要求:
|
|
10
10
|
- `change-id` 必须是 kebab-case:`^[a-z0-9]+(-[a-z0-9]+)*$`
|
|
@@ -4,7 +4,7 @@ description: 私有:切分支并初始化变更工件(可选安装 hooks)
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
目标:
|
|
7
|
-
- 切到分支 `change/<change-id>` 并初始化
|
|
7
|
+
- 切到分支 `change/<change-id>` 并初始化 `.aiws/changes/<change-id>/` 工件
|
|
8
8
|
- 若检测到 `.gitmodules`(git submodules),默认优先使用 `--worktree`(失败则回退为 `--no-switch`),避免切走 superproject 分支导致 submodule 状态混乱
|
|
9
9
|
|
|
10
10
|
要求:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: p-aiws-change-sync
|
|
3
|
-
description: 私有:同步真值基线到 changes/<change-id>(写入 .ws-change.json)
|
|
3
|
+
description: 私有:同步真值基线到 .aiws/changes/<change-id>(写入 .ws-change.json)
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
目标:
|
|
7
|
-
- 将当前真值文件(`AI_PROJECT.md` / `AI_WORKSPACE.md` / `REQUIREMENTS.md`)的 hash 快照同步到
|
|
7
|
+
- 将当前真值文件(`AI_PROJECT.md` / `AI_WORKSPACE.md` / `REQUIREMENTS.md`)的 hash 快照同步到 `.aiws/changes/<change-id>/.ws-change.json`
|
|
8
8
|
|
|
9
9
|
执行(在仓库根目录):
|
|
10
10
|
```bash
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: p-tasks-plan
|
|
3
|
-
description: 原子:tasks 同步(从 changes/<id>/tasks.md 生成 update_plan payload)
|
|
3
|
+
description: 原子:tasks 同步(从 .aiws/changes/<id>/tasks.md 生成 update_plan payload)
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
用中文输出(命令/路径/代码标识符保持原样不翻译)。
|
|
7
7
|
|
|
8
8
|
目标:
|
|
9
|
-
- 用
|
|
9
|
+
- 用 `.aiws/changes/<change-id>/tasks.md` 的 checkbox 任务作为“步骤真值”
|
|
10
10
|
- 生成可直接用于 `update_plan` 的 JSON payload(`pending/in_progress/completed`)
|
|
11
11
|
- 输出当前进度摘要(done/total + 当前 in_progress)
|
|
12
12
|
|
|
13
13
|
约束:
|
|
14
14
|
- 不写入任何 secrets(token、账号、内网端点等不得进入 git)
|
|
15
|
-
- 只读
|
|
15
|
+
- 只读 `.aiws/changes/<id>/tasks.md`(不自动改写 tasks 文件)
|
|
16
16
|
- 未运行不声称已运行
|
|
17
17
|
|
|
18
18
|
执行步骤(建议):
|
|
@@ -20,18 +20,18 @@ description: 原子:tasks 同步(从 changes/<id>/tasks.md 生成 update_pla
|
|
|
20
20
|
2) 推断 `change-id`:
|
|
21
21
|
- 优先从当前分支名读取:`git rev-parse --abbrev-ref HEAD`(期望形如 `change/<change-id>`)
|
|
22
22
|
- 若无法推断:让用户明确本次要同步的 `<change-id>`
|
|
23
|
-
3) 检查 tasks 文件存在:`test -f changes/<change-id>/tasks.md`
|
|
23
|
+
3) 检查 tasks 文件存在:`test -f .aiws/changes/<change-id>/tasks.md`
|
|
24
24
|
4) 输出进度摘要:
|
|
25
25
|
```bash
|
|
26
|
-
python3 tools/ws_tasks_plan.py status --file changes/<change-id>/tasks.md
|
|
26
|
+
python3 tools/ws_tasks_plan.py status --file .aiws/changes/<change-id>/tasks.md
|
|
27
27
|
```
|
|
28
28
|
5) 生成 `update_plan` payload(JSON 输出到 stdout):
|
|
29
29
|
```bash
|
|
30
|
-
python3 tools/ws_tasks_plan.py plan --file changes/<change-id>/tasks.md --explanation "sync tasks.md -> update_plan"
|
|
30
|
+
python3 tools/ws_tasks_plan.py plan --file .aiws/changes/<change-id>/tasks.md --explanation "sync tasks.md -> update_plan"
|
|
31
31
|
```
|
|
32
32
|
6) 调用 `update_plan`,将上一步 JSON 原样作为入参(确保任意时刻最多 1 条 `in_progress`)。
|
|
33
33
|
|
|
34
34
|
输出要求:
|
|
35
35
|
- `Change_ID:` <change-id>
|
|
36
|
-
- `Tasks:`
|
|
36
|
+
- `Tasks:` `.aiws/changes/<change-id>/tasks.md`
|
|
37
37
|
- `Next:` 推荐下一步(通常为继续完善 tasks 或进入 `$ws-dev`)
|