@hadooppei/hwcode 0.2.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.pi/extensions/hwcode.ts +35 -2
  2. package/.pi/extensions/model-providers.ts +1 -2
  3. package/.pi/extensions/workflows/cloud/activation.ts +235 -0
  4. package/.pi/extensions/workflows/cloud/commands.ts +96 -0
  5. package/.pi/extensions/workflows/cloud/events.ts +58 -0
  6. package/.pi/extensions/workflows/cloud/index.ts +17 -0
  7. package/.pi/extensions/workflows/cloud/provider-tools.ts +127 -0
  8. package/.pi/extensions/workflows/cloud/runner-tools.ts +129 -0
  9. package/.pi/extensions/workflows/cloud/runtime.ts +97 -0
  10. package/.pi/extensions/workflows/cloud/shared.ts +213 -0
  11. package/.pi/extensions/workflows/cloud/terraform-tools.ts +126 -0
  12. package/.pi/extensions/workflows/vibe-sdd.ts +300 -0
  13. package/.pi/extensions/workflows.ts +6 -253
  14. package/.pi/lib/runtime/config.ts +3 -7
  15. package/.pi/lib/runtime/defaults.ts +20 -0
  16. package/.pi/lib/runtime/paths.ts +68 -0
  17. package/.pi/lib/runtime/session-state.ts +0 -34
  18. package/.pi/lib/workflow-guard.ts +8 -1
  19. package/.pi/lib/{cloud → workflows/cloud}/adapters.ts +7 -6
  20. package/.pi/lib/workflows/cloud/bundles.ts +358 -0
  21. package/.pi/lib/workflows/cloud/execution.ts +28 -0
  22. package/.pi/lib/{cloud → workflows/cloud}/process.ts +5 -3
  23. package/.pi/lib/workflows/cloud/remote/bootstrap.ts +23 -0
  24. package/.pi/lib/workflows/cloud/remote/connect.ts +83 -0
  25. package/.pi/lib/workflows/cloud/remote/host-key.ts +35 -0
  26. package/.pi/lib/workflows/cloud/remote/profiles.ts +100 -0
  27. package/.pi/lib/workflows/cloud/remote/ssh-transport.ts +104 -0
  28. package/.pi/lib/workflows/cloud/remote/workspace.ts +109 -0
  29. package/.pi/lib/workflows/cloud/template-save.ts +108 -0
  30. package/.pi/lib/workflows/cloud/templates.ts +256 -0
  31. package/.pi/lib/workflows/cloud/terraform/plan.ts +109 -0
  32. package/.pi/lib/workflows/cloud/terraform/policy.ts +36 -0
  33. package/.pi/lib/workflows/cloud/terraform/runner.ts +64 -0
  34. package/.pi/lib/{cloud-vault.ts → workflows/cloud/vault.ts} +26 -24
  35. package/.pi/lib/workflows/cloud/workspace.ts +37 -0
  36. package/.pi/lib/workflows/sdd.ts +11 -0
  37. package/.pi/lib/workflows/state.ts +165 -1
  38. package/.pi/lib/working-directory.ts +0 -58
  39. package/.pi/lib/workspace/access-policy.ts +2 -2
  40. package/.pi/skills/hwcode-cloud/SKILL.md +32 -1
  41. package/.pi/skills/hwcode-sdd/SKILL.md +2 -0
  42. package/README.md +52 -12
  43. package/bin/hwcode.js +2 -6
  44. package/package.json +8 -3
  45. package/.pi/extensions/cloud.ts +0 -587
  46. package/.pi/lib/cloud/templates.ts +0 -148
  47. /package/.pi/lib/{cloud-providers.ts → workflows/cloud/providers.ts} +0 -0
@@ -5,10 +5,18 @@ export const WORKFLOW_EXTERNAL_AUDIT_TYPE = "hwcode-workflow-external-approval";
5
5
 
6
6
  export type WorkflowMode = "vibe" | "sdd" | "cloud";
7
7
  export type WorkflowStatus = "active" | "completed" | "cancelled" | "failed";
8
+ export type SddPhase = "discovery" | "requirements" | "design" | "test-plan" | "tasks" | "tests" | "implementation" | "verification";
9
+
10
+ export interface SddWorkflowProgress {
11
+ phase: SddPhase;
12
+ approvals: Array<{ phase: SddPhase; evidence: string; approvedAt: string }>;
13
+ }
8
14
 
9
15
  export interface FailedApproach {
10
16
  approach: string;
11
17
  reason: string;
18
+ stage?: string;
19
+ attempts?: number;
12
20
  failedAt: string;
13
21
  }
14
22
 
@@ -21,6 +29,56 @@ export interface CloudExecutionStep {
21
29
  completedAt: string;
22
30
  }
23
31
 
32
+ export interface CloudResourceRecord {
33
+ id: string;
34
+ type: string;
35
+ region: string;
36
+ ownership: "existing" | "workflow-created";
37
+ status: "active" | "deleted";
38
+ updatedAt: string;
39
+ }
40
+
41
+ export interface CloudTemplateSource {
42
+ id: string;
43
+ name: string;
44
+ createdAt: string;
45
+ updatedAt: string;
46
+ kind?: "prompt" | "terraform";
47
+ }
48
+
49
+ export interface CloudRunnerSummary {
50
+ id: string;
51
+ name: string;
52
+ vendor: CloudWorkflowDetails["vendor"];
53
+ region: string;
54
+ host: string;
55
+ port: number;
56
+ user: string;
57
+ remoteRoot: string;
58
+ identityType: "instance-role" | "agency" | "ssh-only";
59
+ hostKeyFingerprint: string;
60
+ }
61
+
62
+ export interface TerraformRunState {
63
+ runId: string;
64
+ runnerId: string;
65
+ phase: "synced" | "validated" | "planned" | "applied" | "failed";
66
+ sourceDigest: string;
67
+ sourcePath?: string;
68
+ remoteWorkspace: string;
69
+ planDigest?: string;
70
+ planSummary?: {
71
+ create: number;
72
+ update: number;
73
+ delete: number;
74
+ replace: number;
75
+ read: number;
76
+ sensitiveChangesHidden: number;
77
+ };
78
+ startedAt: string;
79
+ updatedAt: string;
80
+ }
81
+
24
82
  export interface CloudWorkflowDetails {
25
83
  vendor: "aws" | "azure" | "gcp" | "huawei" | "alibaba" | "tencent";
26
84
  deployCurrentProject: boolean;
@@ -28,7 +86,15 @@ export interface CloudWorkflowDetails {
28
86
  allowNonDeleteChanges: boolean;
29
87
  failedApproaches: FailedApproach[];
30
88
  successfulSteps: CloudExecutionStep[];
89
+ resources?: CloudResourceRecord[];
31
90
  terminalFailure: boolean;
91
+ artifactDirectory?: string;
92
+ templateGuidance?: string;
93
+ sourceTemplate?: CloudTemplateSource;
94
+ terraformSourcePath?: string;
95
+ runner?: CloudRunnerSummary;
96
+ runnerPreference?: "automatic" | "deferred" | "existing";
97
+ terraformRun?: TerraformRunState;
32
98
  }
33
99
 
34
100
  export interface WorkflowState {
@@ -40,6 +106,7 @@ export interface WorkflowState {
40
106
  activatedAt: string;
41
107
  updatedAt: string;
42
108
  details?: CloudWorkflowDetails;
109
+ sdd?: SddWorkflowProgress;
43
110
  reason?: string;
44
111
  }
45
112
 
@@ -52,6 +119,47 @@ interface LegacyWorkflowState {
52
119
  deactivatedAt?: string;
53
120
  }
54
121
 
122
+ function isCloudTemplateSource(value: unknown): value is CloudTemplateSource {
123
+ if (!value || typeof value !== "object") return false;
124
+ const data = value as Record<string, unknown>;
125
+ return typeof data.id === "string"
126
+ && typeof data.name === "string"
127
+ && typeof data.createdAt === "string"
128
+ && typeof data.updatedAt === "string"
129
+ && (data.kind === undefined || data.kind === "prompt" || data.kind === "terraform");
130
+ }
131
+
132
+ function isCloudRunnerSummary(value: unknown): value is CloudRunnerSummary {
133
+ if (!value || typeof value !== "object") return false;
134
+ const data = value as Record<string, unknown>;
135
+ return typeof data.id === "string"
136
+ && typeof data.name === "string"
137
+ && typeof data.vendor === "string"
138
+ && typeof data.region === "string"
139
+ && typeof data.host === "string"
140
+ && Number.isInteger(data.port)
141
+ && typeof data.user === "string"
142
+ && typeof data.remoteRoot === "string"
143
+ && typeof data.hostKeyFingerprint === "string"
144
+ && ["instance-role", "agency", "ssh-only"].includes(data.identityType as string);
145
+ }
146
+
147
+ function isTerraformRunState(value: unknown): value is TerraformRunState {
148
+ if (!value || typeof value !== "object") return false;
149
+ const data = value as Record<string, unknown>;
150
+ const summary = data.planSummary;
151
+ return typeof data.runId === "string"
152
+ && typeof data.runnerId === "string"
153
+ && ["synced", "validated", "planned", "applied", "failed"].includes(data.phase as string)
154
+ && typeof data.sourceDigest === "string"
155
+ && typeof data.remoteWorkspace === "string"
156
+ && typeof data.startedAt === "string"
157
+ && typeof data.updatedAt === "string"
158
+ && (data.planDigest === undefined || typeof data.planDigest === "string")
159
+ && (summary === undefined || (summary !== null && typeof summary === "object"
160
+ && Object.values(summary as Record<string, unknown>).every((entry) => Number.isInteger(entry))));
161
+ }
162
+
55
163
  function isCloudDetails(value: unknown): value is CloudWorkflowDetails {
56
164
  if (!value || typeof value !== "object") return false;
57
165
  const data = value as Record<string, unknown>;
@@ -61,7 +169,22 @@ function isCloudDetails(value: unknown): value is CloudWorkflowDetails {
61
169
  && typeof data.allowNonDeleteChanges === "boolean"
62
170
  && Array.isArray(data.failedApproaches)
63
171
  && Array.isArray(data.successfulSteps)
64
- && typeof data.terminalFailure === "boolean";
172
+ && (data.resources === undefined || (Array.isArray(data.resources) && data.resources.every((resource) => {
173
+ if (!resource || typeof resource !== "object") return false;
174
+ const entry = resource as Record<string, unknown>;
175
+ return typeof entry.id === "string" && typeof entry.type === "string" && typeof entry.region === "string"
176
+ && ["existing", "workflow-created"].includes(entry.ownership as string)
177
+ && ["active", "deleted"].includes(entry.status as string)
178
+ && typeof entry.updatedAt === "string";
179
+ })))
180
+ && typeof data.terminalFailure === "boolean"
181
+ && (data.artifactDirectory === undefined || typeof data.artifactDirectory === "string")
182
+ && (data.templateGuidance === undefined || typeof data.templateGuidance === "string")
183
+ && (data.terraformSourcePath === undefined || typeof data.terraformSourcePath === "string")
184
+ && (data.sourceTemplate === undefined || isCloudTemplateSource(data.sourceTemplate))
185
+ && (data.runner === undefined || isCloudRunnerSummary(data.runner))
186
+ && (data.runnerPreference === undefined || ["automatic", "deferred", "existing"].includes(data.runnerPreference as string))
187
+ && (data.terraformRun === undefined || isTerraformRunState(data.terraformRun));
65
188
  }
66
189
 
67
190
  export function decodeWorkflowState(value: unknown): WorkflowState | undefined {
@@ -75,6 +198,11 @@ export function decodeWorkflowState(value: unknown): WorkflowState | undefined {
75
198
  || typeof data.activatedAt !== "string"
76
199
  || typeof data.updatedAt !== "string") return undefined;
77
200
  if (data.mode === "cloud" && !isCloudDetails(data.details)) return undefined;
201
+ if (data.mode === "sdd" && data.sdd !== undefined) {
202
+ const sdd = data.sdd as Record<string, unknown>;
203
+ if (!( ["discovery", "requirements", "design", "test-plan", "tasks", "tests", "implementation", "verification"] as unknown[]).includes(sdd.phase)
204
+ || !Array.isArray(sdd.approvals)) return undefined;
205
+ }
78
206
  return data as unknown as WorkflowState;
79
207
  }
80
208
 
@@ -121,6 +249,42 @@ export function createWorkflowState(
121
249
  phase,
122
250
  activatedAt: now,
123
251
  updatedAt: now,
252
+ ...(mode === "sdd" ? { sdd: { phase: "discovery", approvals: [] } } : {}),
253
+ };
254
+ }
255
+
256
+ export function createCloudWorkflowState(
257
+ root: string,
258
+ vendor: CloudWorkflowDetails["vendor"],
259
+ deployCurrentProject: boolean,
260
+ request: string,
261
+ template?: { source: CloudTemplateSource; guidance: string },
262
+ artifactDirectory?: string,
263
+ ): WorkflowState {
264
+ const now = new Date().toISOString();
265
+ return {
266
+ version: 2,
267
+ status: "active",
268
+ mode: "cloud",
269
+ root,
270
+ phase: "connected",
271
+ activatedAt: now,
272
+ updatedAt: now,
273
+ details: {
274
+ vendor,
275
+ deployCurrentProject,
276
+ request,
277
+ allowNonDeleteChanges: false,
278
+ failedApproaches: [],
279
+ successfulSteps: [],
280
+ resources: [],
281
+ terminalFailure: false,
282
+ ...(artifactDirectory ? { artifactDirectory } : {}),
283
+ ...(template ? {
284
+ templateGuidance: template.guidance,
285
+ sourceTemplate: template.source,
286
+ } : {}),
287
+ },
124
288
  };
125
289
  }
126
290
 
@@ -29,11 +29,6 @@ export interface DirectoryChange {
29
29
  remainder: string;
30
30
  }
31
31
 
32
- export interface ChainedDirectoryChange {
33
- argument: string;
34
- standalone: boolean;
35
- }
36
-
37
32
  const workingDirectories = new Map<string, WorkingDirectoryState>();
38
33
 
39
34
  function isWorkingDirectoryState(value: unknown): value is WorkingDirectoryState {
@@ -190,59 +185,6 @@ export function parseLeadingDirectoryChange(command: string): DirectoryChange |
190
185
  return { argument, remainder: command.slice(index).trimStart() };
191
186
  }
192
187
 
193
- /** Find a standalone `cd` segment in a top-level `&&` or `;` command chain. */
194
- export function findChainedDirectoryChange(command: string): ChainedDirectoryChange | undefined {
195
- const segments: string[] = [];
196
- let segmentStart = 0;
197
- let quote: "'" | '"' | undefined;
198
- let escaped = false;
199
- let nesting = 0;
200
-
201
- for (let index = 0; index < command.length; index += 1) {
202
- const character = command[index];
203
- if (escaped) {
204
- escaped = false;
205
- continue;
206
- }
207
- if (character === "\\" && quote !== "'") {
208
- escaped = true;
209
- continue;
210
- }
211
- if (quote) {
212
- if (character === quote) quote = undefined;
213
- continue;
214
- }
215
- if (character === "'" || character === '"') {
216
- quote = character;
217
- continue;
218
- }
219
- if (character === "(" || character === "{") {
220
- nesting += 1;
221
- continue;
222
- }
223
- if (character === ")" || character === "}") {
224
- nesting = Math.max(0, nesting - 1);
225
- continue;
226
- }
227
- if (nesting > 0) continue;
228
-
229
- const separatorLength = command.slice(index, index + 2) === "&&" ? 2 : character === ";" ? 1 : 0;
230
- if (!separatorLength) continue;
231
- segments.push(command.slice(segmentStart, index));
232
- index += separatorLength - 1;
233
- segmentStart = index + 1;
234
- }
235
- segments.push(command.slice(segmentStart));
236
-
237
- for (const segment of segments) {
238
- const parsed = parseLeadingDirectoryChange(segment);
239
- if (parsed && !parsed.remainder) {
240
- return { argument: parsed.argument, standalone: segments.length === 1 };
241
- }
242
- }
243
- return undefined;
244
- }
245
-
246
188
  export function getActiveWorkflowRoot(entries: readonly SessionEntry[]): string | undefined {
247
189
  return activeWorkflow(entries)?.root;
248
190
  }
@@ -2,7 +2,7 @@ import { homedir, tmpdir } from "node:os";
2
2
 
3
3
  import {
4
4
  findExternalPathReferences,
5
- isPathInsideRoot,
5
+ isPathAllowedWithoutApproval,
6
6
  resolveToolPath,
7
7
  type ExternalPathReference,
8
8
  } from "../workflow-guard.ts";
@@ -21,7 +21,7 @@ export function evaluateToolPathAccess(
21
21
  ): ExternalPathReference[] {
22
22
  if (FILE_PATH_TOOLS.has(request.toolName) && typeof request.input.path === "string") {
23
23
  const resolved = resolveToolPath(root, request.input.path, homedir());
24
- return isPathInsideRoot(root, resolved)
24
+ return isPathAllowedWithoutApproval(root, resolved)
25
25
  ? []
26
26
  : [{ raw: request.input.path, resolved }];
27
27
  }
@@ -11,7 +11,7 @@ Use this skill only after `/hwcode-cloud` has activated the workflow. The activa
11
11
 
12
12
  - Never ask for, display, infer, copy, log, summarize, or place credentials in chat, shell text, source files, environment files, tool arguments, plans, or session artifacts.
13
13
  - Never read `~/.hwcode/cloud/credentials.enc`. The extension owns encryption, decryption, and credential injection.
14
- - Use `hwcode_cloud_exec` for every provider CLI, Terraform/OpenTofu, Pulumi, kubectl, or Helm operation. Direct Bash use for those commands is forbidden.
14
+ - Use `hwcode_cloud_exec` for provider CLI operations. Use `hwcode_runner_prepare` and `hwcode_terraform_*` for Terraform execution. Direct Bash use for those commands is forbidden.
15
15
  - Keep project reads, writes, builds, manifests, and generated artifacts inside the locked project root. External paths require the workflow guard's one-call approval.
16
16
  - Prefer short-lived, least-privilege identities and narrowly scoped roles. Never widen permissions merely to bypass an authorization error without explaining the exact missing permission and obtaining approval.
17
17
  - Do not expose sensitive values returned by a provider. If output unexpectedly contains a secret, do not repeat it; tell the user to rotate it.
@@ -20,6 +20,19 @@ Use this skill only after `/hwcode-cloud` has activated the workflow. The activa
20
20
 
21
21
  The command already collected the provider, deployment choice, objective, and validated account connection. Do not repeat those questions unless the activation context is contradictory.
22
22
 
23
+ ## Artifact workspace
24
+
25
+ The activation context includes a task artifact workspace inside the locked project root. Keep all generated Cloud-task artifacts there; never write them to the project root or invent top-level task directories.
26
+
27
+ The run workspace is retained after the session so `/hwcode-cloud-save-template` can safely extract its validated artifacts. Do not move or duplicate these files into another temporary directory.
28
+
29
+ - `discovery/`: provider CLI skeletons, read-only snapshots, and API input exploration;
30
+ - `terraform/`: Terraform source, module lock files, and generated provider configuration (never state files or tfvars with secrets);
31
+ - `charts/`: Helm chart sources and package archives;
32
+ - `reports/`: sanitized plans, verification results, and final summaries.
33
+
34
+ Provider CLIs run with this workspace as their current directory. When an argument must reference an existing project source file, use its absolute path under the locked project root.
35
+
23
36
  Inspect the project only as needed to determine:
24
37
 
25
38
  - application type, build and runtime requirements;
@@ -43,6 +56,19 @@ Before modifying account resources, present a plan containing:
43
56
 
44
57
  Prefer declarative, reviewable, idempotent infrastructure as code. Use a plan/dry-run command before apply when the selected tooling supports it. Pin important versions and avoid provider defaults that materially affect cost or exposure.
45
58
 
59
+ ### Terraform Runner
60
+
61
+ Do not ask the user to name or manually configure a Runner. If the activation context has no selected Runner and Terraform is required:
62
+
63
+ 1. design a minimal temporary Runner (image, network exposure, instance role/agency, SSH access, estimated cost);
64
+ 2. ask for approval to create those billable cloud resources;
65
+ 3. create them through `hwcode_cloud_exec`, preferring provider identity on the Runner over copying AK/SK;
66
+ 4. install Terraform and `tar` through the instance image or cloud-init, then pass the resulting endpoint to `hwcode_runner_connect`; it selects a local default SSH key, shows standard OpenSSH SHA256 host-key fingerprints for verification, and stores the Runner profile encrypted;
67
+ 5. when the target is private, use the tool's ProxyJump fields with the approved bastion instead of copying credentials or opening broad public access;
68
+ 6. call `hwcode_runner_prepare` to create the managed Runner directories and verify required capabilities, then use only `hwcode_terraform_*` for Terraform.
69
+
70
+ If an existing SSH host is selected, reuse it. Only ask for SSH information when it cannot be derived from the provider result or saved Runner profile.
71
+
46
72
  ## Phase 3: Execute with approval gates
47
73
 
48
74
  For every `hwcode_cloud_exec` call:
@@ -52,6 +78,7 @@ For every `hwcode_cloud_exec` call:
52
78
  - use a stable `approach` name for the current technical strategy;
53
79
  - pass an executable and argument vector, never shell syntax;
54
80
  - inspect the result before proceeding.
81
+ - after discovering, creating, or deleting a resource, call `hwcode_cloud_record_resource` with its non-secret provider identifier, type, region, ownership, and current status. This inventory is required for completion and cleanup reporting.
55
82
 
56
83
  The extension confirms every account-resource create or modification unless the user selects session-wide approval for non-delete changes. A deletion is always confirmed separately, even after that opt-out.
57
84
 
@@ -79,6 +106,8 @@ After three genuinely different approaches fail, stop all execution. Tell the us
79
106
 
80
107
  Do not attempt a fourth approach.
81
108
 
109
+ Read-only inspection and explicitly confirmed cleanup deletions remain allowed after the failure budget is reached; new resource changes do not.
110
+
82
111
  ## Completion
83
112
 
84
113
  On success, summarize:
@@ -89,3 +118,5 @@ On success, summarize:
89
118
  - verification results;
90
119
  - ongoing cost, security, monitoring, backup, and credential-rotation considerations;
91
120
  - rollback and teardown procedure (do not execute teardown unless separately requested and approved).
121
+
122
+ When saving a successful Terraform run, the local Deployment Template is a schema-v2 bundle. It contains filtered Terraform/Helm/discovery artifacts, verification guidance, an exclusion report, and a digest of the actual saved content. Runtime state, plans, tfvars, credentials, and discovered runtime identifiers must not be copied. Reuse is blocked if the bundle content no longer matches its manifest digest; schema-v1 bundles are migrated in memory when read.
@@ -11,6 +11,8 @@ Make the approved specification the source of truth. Move through discovery, spe
11
11
 
12
12
  Confirm that the activation message states a locked project root and Git status. If this skill was invoked directly without the `/hwcode-sdd` project command, do not begin work; ask the user to run `/hwcode-sdd [initial requirement]`. The command confirms the directory, enforces the path boundary, initializes Git when approved, and verifies that the current directory is the repository root.
13
13
 
14
+ The activation starts a persisted `discovery` phase. After completing and presenting each phase artifact, call `hwcode_sdd_advance` for the next phase. The extension asks the user for explicit approval and rejects skipped transitions. Before `tests`, built-in write/edit tools are limited to `.hwcode/specs/`; during `tests`, they may also create test files. Production-file changes begin only in `implementation`.
15
+
14
16
  Use the locked root as the only project workspace. Use in-root paths silently. Explain a genuine external-path need and rely on the one-call approval prompt; never bypass it.
15
17
 
16
18
  Do not implement production behavior until the user has explicitly approved the requirements and then the design, test plan, and task plan. If later evidence exposes ambiguity, return to the earliest affected artifact and obtain approval again.
package/README.md CHANGED
@@ -100,11 +100,19 @@ Start one of the project workflows from the Pi input:
100
100
 
101
101
  Both commands first confirm the current directory and lock project work to that
102
102
  root for the session. In-root operations run normally; each tool call that names
103
- an external path asks for separate approval. `/hwcode-vibe` uses short iterative
104
- build-and-verify loops. `/hwcode-sdd` additionally requires the current directory
103
+ an external path asks for separate approval. The non-storage sink `/dev/null` is
104
+ approval-free even when used by shell redirection. `/hwcode-vibe` uses short
105
+ iterative build-and-verify loops. `/hwcode-sdd` additionally requires the current directory
105
106
  to be the Git repository root, inventories the codebase, resolves requirement
106
107
  questions, and persists approved artifacts under
107
108
  `.hwcode/specs/<requirement-slug>/` before test-first implementation begins.
109
+ SDD phases are persisted and advance one step at a time through an interactive
110
+ approval gate. Before the tests phase, built-in file writes are restricted to
111
+ the spec directory; production writes begin only in implementation.
112
+
113
+ Use `/hwcode` to inspect, complete, or cancel the active workflow. Completing
114
+ SDD is allowed only after verification. Cloud completion warns about any
115
+ workflow-created resources that remain active in its resource inventory.
108
116
 
109
117
  ### Cloud workflow
110
118
 
@@ -135,16 +143,38 @@ confirmation unless the user approves remaining non-delete changes for the
135
143
  session. Resource deletion is always confirmed. After three genuinely distinct
136
144
  technical approaches fail, the workflow stops and reports causes, progress,
137
145
  remaining resources, and local changes instead of attempting a fourth approach.
146
+ Read-only inspection and confirmed cleanup deletion remain available after the
147
+ failure budget is reached.
148
+
149
+ Terraform execution uses a pinned SSH Runner connection. New and existing hosts
150
+ share the same OpenSSH SHA256 host-key confirmation path; private targets can be
151
+ scanned through a verified ProxyJump. Runner execution requires Terraform,
152
+ `tar`, `sha256sum`, a declared cloud workload identity, and an explicit non-local
153
+ Terraform backend. The exact policy-checked file manifest is uploaded and its
154
+ per-file digest is verified remotely before execution.
138
155
 
139
156
  After at least one cloud command succeeds, run
140
157
  `/hwcode-cloud-save-template [name]` to save the objective, validated execution
141
- sequence, and optional lessons learned as a local Prompt Template. Templates are
142
- stored under `~/.hwcode/cloud/prompts/` with user-only permissions. Credential
143
- values are excluded and redacted before steps are persisted.
158
+ sequence, failed approaches, prerequisites, and optional lessons learned as a
159
+ local Prompt Template. A successful managed Terraform apply instead produces a
160
+ schema-v2 Deployment Template bundle containing filtered Terraform, Helm,
161
+ discovery and report artifacts plus verification notes, exclusion reasons, and
162
+ content/metadata integrity digests. Each template records its user-facing name plus creation
163
+ and update timestamps. Its prompt is layered into stable intent, prerequisites,
164
+ the preferred known-good path, expensive failed paths that must not be retried,
165
+ and the guarded execution contract. Prompt Templates are stored under
166
+ `~/.hwcode/cloud/templates/prompts/`; Deployment Template bundles are stored
167
+ under `~/.hwcode/cloud/templates/deployments/`, both with user-only permissions.
168
+ Existing resources under the legacy `~/.hwcode/cloud/prompts/` and
169
+ `~/.hwcode/cloud/templates/` locations remain discoverable and are updated in
170
+ place when reused. Credential values are
171
+ excluded and redacted before steps are persisted.
144
172
 
145
173
  Use `/hwcode-cloud-template [additional instructions]` to select and start a
146
- saved template immediately. `/hwcode-cloud` also offers saved templates when it
147
- starts without an inline request. The files use a Pi-compatible
174
+ saved template by name and last-updated time. `/hwcode-cloud` also offers saved
175
+ templates when it starts without an inline request. If a session started from a
176
+ template, saving again asks whether to update that source template or create a
177
+ new one. The files use a Pi-compatible
148
178
  Markdown/frontmatter format, but HWCode intentionally keeps them out of Pi
149
179
  resource discovery so each saved template does not become another slash
150
180
  command. Templates are extension-private resources and always run through the
@@ -154,14 +184,24 @@ Cloud approval guards.
154
184
  ## Internal architecture
155
185
 
156
186
  Extensions under `.pi/extensions/` are Pi-facing adapters: they register events,
157
- commands, tools, and UI. Reusable behavior lives under `.pi/lib/`:
158
-
159
- - `runtime/` owns layered/replacing configuration and session-state primitives.
160
- - `workflows/` owns the shared Vibe/SDD/Cloud lifecycle schema.
187
+ commands, tools, and UI. `extensions/workflows.ts` is the single Workflow entry;
188
+ Vibe/SDD live in `extensions/workflows/vibe-sdd.ts`, while the Cloud adapter is
189
+ split under `extensions/workflows/cloud/` into activation, commands, events,
190
+ Provider tools, Runner tools, Terraform tools, shared UI, and session runtime.
191
+ Reusable behavior lives under `.pi/lib/`:
192
+
193
+ - `runtime/` owns layered/replacing configuration, canonical runtime paths, and session-state primitives.
194
+ - `workflows/` owns the shared lifecycle schema; `workflows/cloud/` contains the complete Cloud workflow domain, including its `remote/` and `terraform/` execution components.
161
195
  - `workspace/` owns tool and command path-boundary decisions.
162
- - `cloud/` owns provider adapters, isolated processes, and prompt templates.
163
196
  - `context/` and `models/` own compaction and provider-configuration policy.
164
197
 
198
+ Runtime data is separated by ownership: project SDD specifications live under
199
+ `.hwcode/specs/`, retained Cloud run artifacts live under
200
+ `.hwcode/cloud/runs/<run-id>/`, and user-private credentials, SSH trust, and
201
+ templates live under `~/.hwcode/cloud/`. Cloud run artifacts are deliberately
202
+ retained for inspection and later template extraction; HWCode does not apply a
203
+ time-based cleanup policy.
204
+
165
205
  `settings.json` is layered as defaults → profile → project for settings such as
166
206
  context and hidden commands. `welcome.json` uses a single replacing resource.
167
207
  `model-providers.json` is deliberately package-scoped so a project cannot
package/bin/hwcode.js CHANGED
@@ -57,7 +57,8 @@ HWCode 交互命令:
57
57
  /hwcode-sdd [需求] 启动测试优先的 Spec-Driven workflow
58
58
  /hwcode-cloud [需求] 启动凭据隔离、变更审批的云部署 workflow
59
59
  /hwcode-cloud-template [补充] 从本地成功模板启动 Cloud workflow
60
- /hwcode-cloud-save-template 将成功执行路径保存为本地 Prompt Template
60
+ /hwcode-cloud-save-template 保存或更新 Prompt/Terraform Deployment Template
61
+ /hwcode 查看、完成或取消当前 workflow
61
62
  /cd <目录> 持久切换当前会话工作目录
62
63
  /model 选择模型
63
64
  /login 登录或配置模型提供商
@@ -104,17 +105,12 @@ const piManagementCommands = new Set([
104
105
  ]);
105
106
 
106
107
  const profileArgs = [
107
- "--tui-mode",
108
- "fullscreen",
109
- "--thinking",
110
- "medium",
111
108
  "--append-system-prompt",
112
109
  join(profileDirectory, "APPEND_SYSTEM.md"),
113
110
  ];
114
111
 
115
112
  for (const extension of [
116
113
  "command-filter.ts",
117
- "cloud.ts",
118
114
  "context-policy.ts",
119
115
  "cwd.ts",
120
116
  "footer-tps.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hadooppei/hwcode",
3
- "version": "0.2.1",
3
+ "version": "1.0.0",
4
4
  "description": "A customizable terminal coding agent with local-model support and HWCode workflows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  ".pi/extensions",
15
15
  ".pi/lib",
16
16
  "!.pi/lib/*.test.ts",
17
+ "!.pi/lib/**/*.test.ts",
17
18
  ".pi/model-providers.json",
18
19
  ".pi/skills/hwcode-sdd",
19
20
  ".pi/skills/hwcode-vibe",
@@ -24,9 +25,10 @@
24
25
  "scripts": {
25
26
  "start": "node --env-file-if-exists=.env ./bin/hwcode.js",
26
27
  "pi": "node --env-file-if-exists=.env ./bin/hwcode.js",
27
- "test": "node --test .pi/lib/*.test.ts",
28
+ "test": "node scripts/run-tests.mjs",
29
+ "typecheck": "tsc -p tsconfig.json",
28
30
  "test:workflows": "npm test",
29
- "prepack": "npm test && node scripts/audit-package.mjs"
31
+ "prepack": "npm run typecheck && npm test && node scripts/audit-package.mjs"
30
32
  },
31
33
  "engines": {
32
34
  "node": ">=22.19.0"
@@ -48,5 +50,8 @@
48
50
  "@earendil-works/pi-ai": "0.84.2",
49
51
  "@earendil-works/pi-coding-agent": "0.84.2",
50
52
  "@earendil-works/pi-tui": "0.84.2"
53
+ },
54
+ "devDependencies": {
55
+ "typescript": "^5.9.2"
51
56
  }
52
57
  }