@bvdm/delano 0.2.3 → 0.2.5
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/.delano/viewer/README.md +3 -2
- package/.delano/viewer/public/app.js +13 -1
- package/.delano/viewer/public/app.jsx +2312 -0
- package/.delano/viewer/public/delano-mark.svg +4 -0
- package/.delano/viewer/public/index.html +12 -14
- package/.delano/viewer/public/styles.css +1005 -833
- package/.delano/viewer/server.js +46 -5
- package/README.md +63 -3
- package/assets/install-manifest.json +7 -0
- package/assets/payload/.agents/adapters/manifest.schema.json +103 -0
- package/assets/payload/.agents/adapters/spec-kit/adapter.json +71 -0
- package/assets/payload/.agents/hooks/README.md +6 -1
- package/assets/payload/.agents/hooks/codex-session-status.js +123 -0
- package/assets/payload/.agents/schemas/status-transitions.json +35 -0
- package/assets/payload/.agents/scripts/README.md +1 -1
- package/assets/payload/.agents/scripts/check-status-transitions.mjs +171 -2
- package/assets/payload/.agents/scripts/pm/import-spec-kit.sh +605 -0
- package/assets/payload/.agents/scripts/pm/init.sh +31 -2
- package/assets/payload/.agents/scripts/pm/research.sh +296 -0
- package/assets/payload/.agents/scripts/pm/status.sh +135 -28
- package/assets/payload/.agents/scripts/pm/validate.sh +16 -0
- package/assets/payload/.codex/hooks.json +17 -0
- package/assets/payload/.delano/viewer/README.md +3 -2
- package/assets/payload/.delano/viewer/public/app.js +13 -1
- package/assets/payload/.delano/viewer/public/index.html +12 -14
- package/assets/payload/.delano/viewer/public/styles.css +1005 -833
- package/assets/payload/.delano/viewer/server.js +46 -5
- package/assets/payload/.project/templates/decisions.md +18 -0
- package/assets/payload/.project/templates/plan.md +17 -0
- package/assets/payload/.project/templates/spec.md +12 -0
- package/assets/payload/.project/templates/task.md +6 -0
- package/assets/payload/.project/templates/workstream.md +1 -0
- package/package.json +4 -2
- package/src/cli/commands/install.js +2 -1
- package/src/cli/commands/state.js +689 -0
- package/src/cli/commands/viewer.js +2 -1
- package/src/cli/commands/wrapper.js +29 -5
- package/src/cli/index.js +120 -7
- package/src/cli/lib/install.js +179 -2
- package/src/cli/lib/project-state.js +918 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage:
|
|
7
|
+
import-spec-kit.sh <slug> <source-md> [options]
|
|
8
|
+
import-spec-kit.sh <slug> <source-md> [project-name] [owner] [lead]
|
|
9
|
+
|
|
10
|
+
Creates a planned Delano project from the first supported Spec Kit-style markdown fixture.
|
|
11
|
+
|
|
12
|
+
Required arguments:
|
|
13
|
+
slug Target Delano project slug in kebab-case
|
|
14
|
+
source-md Path to a markdown source artifact
|
|
15
|
+
|
|
16
|
+
Options:
|
|
17
|
+
--name <project-name> Project name override
|
|
18
|
+
--owner <owner> Project owner, defaults to team
|
|
19
|
+
--lead <lead> Project lead, defaults to owner
|
|
20
|
+
--no-validate Create artifacts without running Delano validation
|
|
21
|
+
--json Print a single machine-readable JSON result
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
|
|
24
|
+
Agent notes:
|
|
25
|
+
- Prefer named options over positional metadata.
|
|
26
|
+
- Use --json when another agent/tool will parse the result.
|
|
27
|
+
- The command refuses to overwrite an existing .project/projects/<slug>/ folder.
|
|
28
|
+
- Generated artifacts stay planned/ready and still require Delano evidence gates.
|
|
29
|
+
USAGE
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
resolve_python() {
|
|
33
|
+
if command -v python3 >/dev/null 2>&1 && python3 -c "import sys" >/dev/null 2>&1; then
|
|
34
|
+
PYTHON_CMD=(python3)
|
|
35
|
+
elif command -v py >/dev/null 2>&1 && py -3 -c "import sys" >/dev/null 2>&1; then
|
|
36
|
+
PYTHON_CMD=(py -3)
|
|
37
|
+
elif command -v python >/dev/null 2>&1 && python -c "import sys" >/dev/null 2>&1; then
|
|
38
|
+
PYTHON_CMD=(python)
|
|
39
|
+
else
|
|
40
|
+
echo "Error: Python runtime not found. Install python3, python, or py -3." >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
resolve_python
|
|
46
|
+
|
|
47
|
+
json_escape() {
|
|
48
|
+
"${PYTHON_CMD[@]}" -c 'import json,sys; print(json.dumps(sys.stdin.read().rstrip("\n")))'
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if [[ "${1:-}" == "" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
52
|
+
usage
|
|
53
|
+
exit 0
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
if [[ "${2:-}" == "" ]]; then
|
|
57
|
+
usage
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
slug="$1"
|
|
62
|
+
source_md="$2"
|
|
63
|
+
shift 2
|
|
64
|
+
|
|
65
|
+
project_name=""
|
|
66
|
+
owner="team"
|
|
67
|
+
lead=""
|
|
68
|
+
validate="true"
|
|
69
|
+
json="false"
|
|
70
|
+
positional=()
|
|
71
|
+
|
|
72
|
+
while [[ $# -gt 0 ]]; do
|
|
73
|
+
case "$1" in
|
|
74
|
+
--name)
|
|
75
|
+
project_name="${2:-}"
|
|
76
|
+
if [[ -z "$project_name" ]]; then echo "Error: --name requires a value"; exit 1; fi
|
|
77
|
+
shift 2
|
|
78
|
+
;;
|
|
79
|
+
--owner)
|
|
80
|
+
owner="${2:-}"
|
|
81
|
+
if [[ -z "$owner" ]]; then echo "Error: --owner requires a value"; exit 1; fi
|
|
82
|
+
shift 2
|
|
83
|
+
;;
|
|
84
|
+
--lead)
|
|
85
|
+
lead="${2:-}"
|
|
86
|
+
if [[ -z "$lead" ]]; then echo "Error: --lead requires a value"; exit 1; fi
|
|
87
|
+
shift 2
|
|
88
|
+
;;
|
|
89
|
+
--no-validate)
|
|
90
|
+
validate="false"
|
|
91
|
+
shift
|
|
92
|
+
;;
|
|
93
|
+
--json)
|
|
94
|
+
json="true"
|
|
95
|
+
shift
|
|
96
|
+
;;
|
|
97
|
+
-h|--help)
|
|
98
|
+
usage
|
|
99
|
+
exit 0
|
|
100
|
+
;;
|
|
101
|
+
--)
|
|
102
|
+
shift
|
|
103
|
+
while [[ $# -gt 0 ]]; do positional+=("$1"); shift; done
|
|
104
|
+
;;
|
|
105
|
+
--*)
|
|
106
|
+
echo "Error: unknown option: $1"
|
|
107
|
+
exit 1
|
|
108
|
+
;;
|
|
109
|
+
*)
|
|
110
|
+
positional+=("$1")
|
|
111
|
+
shift
|
|
112
|
+
;;
|
|
113
|
+
esac
|
|
114
|
+
done
|
|
115
|
+
|
|
116
|
+
# Backward-compatible positional metadata: [project-name] [owner] [lead].
|
|
117
|
+
if [[ ${#positional[@]} -gt 0 && -z "$project_name" ]]; then
|
|
118
|
+
project_name="${positional[0]}"
|
|
119
|
+
fi
|
|
120
|
+
if [[ ${#positional[@]} -gt 1 && "$owner" == "team" ]]; then
|
|
121
|
+
owner="${positional[1]}"
|
|
122
|
+
fi
|
|
123
|
+
if [[ ${#positional[@]} -gt 2 && -z "$lead" ]]; then
|
|
124
|
+
lead="${positional[2]}"
|
|
125
|
+
fi
|
|
126
|
+
if [[ ${#positional[@]} -gt 3 ]]; then
|
|
127
|
+
echo "Error: too many positional arguments"
|
|
128
|
+
exit 1
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
lead="${lead:-$owner}"
|
|
132
|
+
|
|
133
|
+
if [[ ! "$slug" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then
|
|
134
|
+
echo "Error: slug must be kebab-case"
|
|
135
|
+
exit 1
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
invocation_cwd="$PWD"
|
|
139
|
+
root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
140
|
+
if [[ "$source_md" != /* ]]; then
|
|
141
|
+
source_md="$invocation_cwd/$source_md"
|
|
142
|
+
fi
|
|
143
|
+
cd "$root"
|
|
144
|
+
|
|
145
|
+
if [[ ! -f "$source_md" ]]; then
|
|
146
|
+
echo "Error: source markdown not found: $source_md"
|
|
147
|
+
exit 1
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
project_dir=".project/projects/$slug"
|
|
151
|
+
if [[ -d "$project_dir" ]]; then
|
|
152
|
+
echo "Error: project already exists at $project_dir"
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
"${PYTHON_CMD[@]}" - "$slug" "$source_md" "$project_name" "$owner" "$lead" <<'PY'
|
|
157
|
+
import re
|
|
158
|
+
import sys
|
|
159
|
+
from datetime import datetime, timezone
|
|
160
|
+
from pathlib import Path
|
|
161
|
+
|
|
162
|
+
slug, source_arg, project_name_arg, owner, lead = sys.argv[1:]
|
|
163
|
+
source_path = Path(source_arg)
|
|
164
|
+
root = Path.cwd()
|
|
165
|
+
source_text = source_path.read_text(encoding="utf-8")
|
|
166
|
+
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
167
|
+
today = now[:10]
|
|
168
|
+
|
|
169
|
+
def write_file(path, text):
|
|
170
|
+
with path.open("w", encoding="utf-8", newline="\n") as handle:
|
|
171
|
+
handle.write(text)
|
|
172
|
+
|
|
173
|
+
heading_match = re.search(r"^#\s+(?:Specification|Spec):\s+(.+?)\s*$", source_text, re.MULTILINE | re.IGNORECASE)
|
|
174
|
+
if project_name_arg:
|
|
175
|
+
project_name = project_name_arg.strip()
|
|
176
|
+
elif heading_match:
|
|
177
|
+
project_name = heading_match.group(1).strip()
|
|
178
|
+
else:
|
|
179
|
+
project_name = slug.replace("-", " ").title()
|
|
180
|
+
|
|
181
|
+
sections = {}
|
|
182
|
+
current = None
|
|
183
|
+
for line in source_text.splitlines():
|
|
184
|
+
match = re.match(r"^##\s+(.+?)\s*$", line)
|
|
185
|
+
if match:
|
|
186
|
+
current = match.group(1).strip().lower()
|
|
187
|
+
sections[current] = []
|
|
188
|
+
continue
|
|
189
|
+
if current:
|
|
190
|
+
sections[current].append(line)
|
|
191
|
+
|
|
192
|
+
for key in list(sections):
|
|
193
|
+
sections[key] = "\n".join(sections[key]).strip()
|
|
194
|
+
|
|
195
|
+
def section(*names):
|
|
196
|
+
for name in names:
|
|
197
|
+
value = sections.get(name.lower())
|
|
198
|
+
if value:
|
|
199
|
+
return value
|
|
200
|
+
return ""
|
|
201
|
+
|
|
202
|
+
def bullet_lines(text):
|
|
203
|
+
items = []
|
|
204
|
+
for line in text.splitlines():
|
|
205
|
+
stripped = line.strip()
|
|
206
|
+
if stripped.startswith("- "):
|
|
207
|
+
items.append(stripped)
|
|
208
|
+
return items
|
|
209
|
+
|
|
210
|
+
user_stories = bullet_lines(section("User Stories"))
|
|
211
|
+
acceptance = bullet_lines(section("Acceptance Scenarios"))
|
|
212
|
+
requirements = bullet_lines(section("Requirements"))
|
|
213
|
+
non_functional = bullet_lines(section("Non-Functional Requirements"))
|
|
214
|
+
assumptions = bullet_lines(section("Assumptions"))
|
|
215
|
+
clarifications = bullet_lines(section("Clarifications", "Needs Clarification"))
|
|
216
|
+
implementation_plan = bullet_lines(section("Implementation Plan"))
|
|
217
|
+
raw_tasks = bullet_lines(section("Tasks"))
|
|
218
|
+
|
|
219
|
+
recognized_content = user_stories + acceptance + requirements + non_functional + assumptions + clarifications + implementation_plan + raw_tasks
|
|
220
|
+
if not recognized_content:
|
|
221
|
+
raise SystemExit("unsupported Spec Kit-style source: expected at least one recognized section such as User Stories, Acceptance Scenarios, Requirements, Implementation Plan, or Tasks")
|
|
222
|
+
|
|
223
|
+
project_dir = root / ".project" / "projects" / slug
|
|
224
|
+
(project_dir / "tasks").mkdir(parents=True)
|
|
225
|
+
(project_dir / "workstreams").mkdir()
|
|
226
|
+
(project_dir / "updates").mkdir()
|
|
227
|
+
|
|
228
|
+
source_display = source_path.as_posix()
|
|
229
|
+
if source_path.is_absolute():
|
|
230
|
+
try:
|
|
231
|
+
source_display = source_path.resolve().relative_to(root).as_posix()
|
|
232
|
+
except ValueError:
|
|
233
|
+
source_display = "external markdown source"
|
|
234
|
+
|
|
235
|
+
def md_list(items, fallback="- None recorded."):
|
|
236
|
+
return "\n".join(items) if items else fallback
|
|
237
|
+
|
|
238
|
+
def yaml_scalar(value):
|
|
239
|
+
return str(value).replace("\n", " ").replace(":", " -")
|
|
240
|
+
|
|
241
|
+
spec = f"""---
|
|
242
|
+
name: {yaml_scalar(project_name)}
|
|
243
|
+
slug: {slug}
|
|
244
|
+
owner: {yaml_scalar(owner)}
|
|
245
|
+
status: planned
|
|
246
|
+
created: {now}
|
|
247
|
+
updated: {now}
|
|
248
|
+
outcome: Imported Spec Kit-style intent is normalized into Delano delivery contracts and validated before execution.
|
|
249
|
+
uncertainty: medium
|
|
250
|
+
probe_required: true
|
|
251
|
+
probe_status: pending
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
# Spec: {project_name}
|
|
255
|
+
|
|
256
|
+
## Executive Summary
|
|
257
|
+
|
|
258
|
+
Imported from a Spec Kit-style markdown artifact. This project is planned until clarification, probe, and validation gates confirm the generated contracts are execution-ready.
|
|
259
|
+
|
|
260
|
+
## Problem and Users
|
|
261
|
+
|
|
262
|
+
{md_list(user_stories)}
|
|
263
|
+
|
|
264
|
+
## Outcome and Success Metrics
|
|
265
|
+
|
|
266
|
+
Acceptance scenarios imported as success signals:
|
|
267
|
+
|
|
268
|
+
{md_list(acceptance)}
|
|
269
|
+
|
|
270
|
+
## User Stories
|
|
271
|
+
|
|
272
|
+
{md_list(user_stories)}
|
|
273
|
+
|
|
274
|
+
## Acceptance Scenarios
|
|
275
|
+
|
|
276
|
+
{md_list(acceptance)}
|
|
277
|
+
|
|
278
|
+
## Scope
|
|
279
|
+
|
|
280
|
+
### In Scope
|
|
281
|
+
|
|
282
|
+
{md_list(requirements)}
|
|
283
|
+
|
|
284
|
+
### Out of Scope
|
|
285
|
+
|
|
286
|
+
- Automatic execution before Delano validation and approval.
|
|
287
|
+
- Automatic external sync writes.
|
|
288
|
+
|
|
289
|
+
## Functional Requirements
|
|
290
|
+
|
|
291
|
+
{md_list(requirements)}
|
|
292
|
+
|
|
293
|
+
## Non-Functional Requirements
|
|
294
|
+
|
|
295
|
+
{md_list(non_functional)}
|
|
296
|
+
|
|
297
|
+
## Assumptions
|
|
298
|
+
|
|
299
|
+
{md_list(assumptions)}
|
|
300
|
+
|
|
301
|
+
## Needs Clarification
|
|
302
|
+
|
|
303
|
+
{md_list(clarifications)}
|
|
304
|
+
|
|
305
|
+
## Hypotheses and Unknowns
|
|
306
|
+
|
|
307
|
+
Assumptions imported from source:
|
|
308
|
+
|
|
309
|
+
{md_list(assumptions)}
|
|
310
|
+
|
|
311
|
+
## Touchpoints to Exercise
|
|
312
|
+
|
|
313
|
+
- Generated Delano project contracts.
|
|
314
|
+
- Task dependency and evidence validation.
|
|
315
|
+
- Import update note.
|
|
316
|
+
|
|
317
|
+
## Probe Findings
|
|
318
|
+
|
|
319
|
+
Pending. Run a delivery probe before activating this spec.
|
|
320
|
+
|
|
321
|
+
## Footguns Discovered
|
|
322
|
+
|
|
323
|
+
- Imported intent may contain assumptions that need operator confirmation.
|
|
324
|
+
- Imported tasks may not include enough evidence detail for closure.
|
|
325
|
+
|
|
326
|
+
## Remaining Unknowns
|
|
327
|
+
|
|
328
|
+
{md_list(clarifications)}
|
|
329
|
+
|
|
330
|
+
## Dependencies
|
|
331
|
+
|
|
332
|
+
- Source artifact: `{source_display}`
|
|
333
|
+
|
|
334
|
+
## Approval Notes
|
|
335
|
+
|
|
336
|
+
Imported by `delano import-spec-kit`. Review before activation.
|
|
337
|
+
"""
|
|
338
|
+
write_file(project_dir / "spec.md", spec)
|
|
339
|
+
|
|
340
|
+
plan = f"""---
|
|
341
|
+
name: {yaml_scalar(project_name)}
|
|
342
|
+
status: planned
|
|
343
|
+
lead: {yaml_scalar(lead)}
|
|
344
|
+
created: {now}
|
|
345
|
+
updated: {now}
|
|
346
|
+
linear_project_id:
|
|
347
|
+
risk_level: medium
|
|
348
|
+
spec_status_at_plan_time: planned
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
# Delivery Plan: {project_name}
|
|
352
|
+
|
|
353
|
+
## What Changed After Probe
|
|
354
|
+
|
|
355
|
+
No probe has been run yet. This plan was imported from a Spec Kit-style source and requires review.
|
|
356
|
+
|
|
357
|
+
## Architecture Decisions
|
|
358
|
+
|
|
359
|
+
Imported implementation plan:
|
|
360
|
+
|
|
361
|
+
{md_list(implementation_plan)}
|
|
362
|
+
|
|
363
|
+
## Probe-Driven Architecture Changes
|
|
364
|
+
|
|
365
|
+
Pending probe.
|
|
366
|
+
|
|
367
|
+
## Workstream Design
|
|
368
|
+
|
|
369
|
+
- WS-A Imported Delivery Foundation: first normalized workstream for imported tasks.
|
|
370
|
+
|
|
371
|
+
## Milestone Strategy
|
|
372
|
+
|
|
373
|
+
1. Review imported spec and clarify open questions.
|
|
374
|
+
2. Run required probe.
|
|
375
|
+
3. Execute ready tasks with Delano evidence gates.
|
|
376
|
+
|
|
377
|
+
## Rollout Strategy
|
|
378
|
+
|
|
379
|
+
Start with local validation and evidence collection. Do not sync externally until identity mappings are reviewed.
|
|
380
|
+
|
|
381
|
+
## Test Strategy
|
|
382
|
+
|
|
383
|
+
- Run `delano validate` after import.
|
|
384
|
+
- Add task-specific tests before closure.
|
|
385
|
+
|
|
386
|
+
## Rollback Strategy
|
|
387
|
+
|
|
388
|
+
If the import is wrong, remove `.project/projects/{slug}` before external sync or activation.
|
|
389
|
+
|
|
390
|
+
## Remaining Delivery Risks
|
|
391
|
+
|
|
392
|
+
- Source assumptions may be incomplete.
|
|
393
|
+
- Imported task boundaries may need workstream refinement.
|
|
394
|
+
- Clarifications may block activation.
|
|
395
|
+
"""
|
|
396
|
+
write_file(project_dir / "plan.md", plan)
|
|
397
|
+
|
|
398
|
+
write_file(project_dir / "decisions.md", "# Decisions\n\nTrack key project decisions with context and rationale.\n")
|
|
399
|
+
|
|
400
|
+
workstream = f"""---
|
|
401
|
+
name: WS-A Imported Delivery Foundation
|
|
402
|
+
owner: {yaml_scalar(owner)}
|
|
403
|
+
status: planned
|
|
404
|
+
created: {now}
|
|
405
|
+
updated: {now}
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
# Workstream: WS-A Imported Delivery Foundation
|
|
409
|
+
|
|
410
|
+
## Objective
|
|
411
|
+
|
|
412
|
+
Normalize and execute the imported Spec Kit-style task set under Delano governance.
|
|
413
|
+
|
|
414
|
+
## Owned Files/Areas
|
|
415
|
+
|
|
416
|
+
- `.project/projects/{slug}/`
|
|
417
|
+
|
|
418
|
+
## Dependencies
|
|
419
|
+
|
|
420
|
+
- Source artifact review.
|
|
421
|
+
- Delano validation.
|
|
422
|
+
|
|
423
|
+
## Risks
|
|
424
|
+
|
|
425
|
+
- Imported tasks may require clarification before execution.
|
|
426
|
+
- Parallel markers are hints and still need conflict review.
|
|
427
|
+
|
|
428
|
+
## Handoff Criteria
|
|
429
|
+
|
|
430
|
+
- Tasks have evidence logs.
|
|
431
|
+
- Validation passes before closure.
|
|
432
|
+
"""
|
|
433
|
+
write_file(project_dir / "workstreams" / "WS-A-imported-delivery-foundation.md", workstream)
|
|
434
|
+
|
|
435
|
+
def slugify(text):
|
|
436
|
+
text = re.sub(r"^T\d+\s+", "", text.strip(), flags=re.IGNORECASE)
|
|
437
|
+
text = re.sub(r"[^a-zA-Z0-9]+", "-", text.lower()).strip("-")
|
|
438
|
+
return text or "imported-task"
|
|
439
|
+
|
|
440
|
+
if not raw_tasks:
|
|
441
|
+
raw_tasks = ["- [ ] Review imported Spec Kit artifact and define executable Delano tasks."]
|
|
442
|
+
|
|
443
|
+
acceptance_ids = []
|
|
444
|
+
for index, item in enumerate(acceptance, start=1):
|
|
445
|
+
match = re.search(r"\bAC[-_ ]?(\d{1,3})\b", item, re.IGNORECASE)
|
|
446
|
+
acceptance_ids.append(f"AC-{int(match.group(1)):03d}" if match else f"AC-{index:03d}")
|
|
447
|
+
|
|
448
|
+
def parse_task(raw, index):
|
|
449
|
+
text = raw.strip()
|
|
450
|
+
parallel = bool(re.search(r"\[(?:P|p)\]", text))
|
|
451
|
+
source_task_match = re.search(r"\bT[-_ ]?(\d{1,4})\b", text, re.IGNORECASE)
|
|
452
|
+
story_match = re.search(r"\b(?:US|Story)[-_ ]?(\d{1,3})\b", text, re.IGNORECASE)
|
|
453
|
+
story_id = f"US-{int(story_match.group(1)):03d}" if story_match else ""
|
|
454
|
+
|
|
455
|
+
title = re.sub(r"^-\s*", "", text).strip()
|
|
456
|
+
title = re.sub(r"^\[(?: |x|X|P|p)\]\s*", "", title).strip()
|
|
457
|
+
title = re.sub(r"\[(?:P|p)\]", "", title).strip()
|
|
458
|
+
title = re.sub(r"\[(?:US|Story)[-_ ]?\d{1,3}\]", "", title, flags=re.IGNORECASE).strip()
|
|
459
|
+
title = re.sub(r"^T[-_ ]?\d{1,4}[:.)-]?\s*", "", title, flags=re.IGNORECASE).strip()
|
|
460
|
+
title = re.sub(r"^\[[^\]]+\]\s*", "", title).strip()
|
|
461
|
+
title = title or f"Review imported task {index}"
|
|
462
|
+
|
|
463
|
+
vague = bool(re.search(r"\b(tbd|todo|clarify|needs clarification|unknown|investigate|research)\b", raw, re.IGNORECASE))
|
|
464
|
+
generated_review_task = index == 1 and "Review imported Spec Kit artifact" in raw
|
|
465
|
+
blocked = bool(clarifications) or vague or generated_review_task
|
|
466
|
+
status = "blocked" if blocked else "ready"
|
|
467
|
+
reason = "Open clarifications or vague source wording require review before execution." if blocked else "No source clarification blocker detected by importer."
|
|
468
|
+
source_task_id = f"T{int(source_task_match.group(1)):03d}" if source_task_match else ""
|
|
469
|
+
return title, parallel, status, reason, story_id, source_task_id
|
|
470
|
+
|
|
471
|
+
for index, raw in enumerate(raw_tasks, start=1):
|
|
472
|
+
title, parallel, status, block_reason, story_id, source_task_id = parse_task(raw, index)
|
|
473
|
+
task_id = f"T-{index:03d}"
|
|
474
|
+
task_slug = slugify(title)
|
|
475
|
+
acceptance_yaml = "[" + ", ".join(acceptance_ids) + "]" if acceptance_ids else "[]"
|
|
476
|
+
blocker_frontmatter = ""
|
|
477
|
+
blocker_section = ""
|
|
478
|
+
if status == "blocked":
|
|
479
|
+
blocker_frontmatter = f"blocked_owner: {yaml_scalar(owner)}\nblocked_check_back: {today}\n"
|
|
480
|
+
blocker_section = f"\n## Blocker\n\n{block_reason}\n"
|
|
481
|
+
task = f"""---
|
|
482
|
+
id: {task_id}
|
|
483
|
+
name: {yaml_scalar(title)}
|
|
484
|
+
status: {status}
|
|
485
|
+
workstream: WS-A
|
|
486
|
+
created: {now}
|
|
487
|
+
updated: {now}
|
|
488
|
+
linear_issue_id:
|
|
489
|
+
github_issue:
|
|
490
|
+
github_pr:
|
|
491
|
+
depends_on: []
|
|
492
|
+
conflicts_with: []
|
|
493
|
+
parallel: {str(parallel).lower()}
|
|
494
|
+
priority: medium
|
|
495
|
+
estimate: M
|
|
496
|
+
story_id: {story_id}
|
|
497
|
+
acceptance_criteria_ids: {acceptance_yaml}
|
|
498
|
+
{blocker_frontmatter}---
|
|
499
|
+
|
|
500
|
+
# Task: {title}
|
|
501
|
+
|
|
502
|
+
## Description
|
|
503
|
+
|
|
504
|
+
Imported from Spec Kit-style source task: `{raw}`
|
|
505
|
+
|
|
506
|
+
## Acceptance Criteria
|
|
507
|
+
|
|
508
|
+
- [ ] Task has been reviewed against the imported acceptance scenarios.
|
|
509
|
+
- [ ] Implementation satisfies relevant Delano evidence requirements.
|
|
510
|
+
|
|
511
|
+
## Traceability
|
|
512
|
+
|
|
513
|
+
- Source task id: {source_task_id or "none detected"}
|
|
514
|
+
- Story: {story_id or "none detected"}
|
|
515
|
+
- Acceptance criteria: {", ".join(acceptance_ids) if acceptance_ids else "none detected"}
|
|
516
|
+
{blocker_section}
|
|
517
|
+
## Technical Notes
|
|
518
|
+
|
|
519
|
+
- Source artifact: `{source_display}`
|
|
520
|
+
- Parallel marker imported: `{str(parallel).lower()}`
|
|
521
|
+
- Initial status: `{status}`
|
|
522
|
+
|
|
523
|
+
## Definition of Done
|
|
524
|
+
|
|
525
|
+
- [ ] Implementation complete
|
|
526
|
+
- [ ] Tests pass
|
|
527
|
+
- [ ] Review complete
|
|
528
|
+
- [ ] Docs updated if behavior is user-visible
|
|
529
|
+
- [ ] Evidence recorded
|
|
530
|
+
|
|
531
|
+
## Evidence Log
|
|
532
|
+
|
|
533
|
+
- {now}: Imported from Spec Kit-style markdown by `delano import-spec-kit`.
|
|
534
|
+
"""
|
|
535
|
+
write_file(project_dir / "tasks" / f"{task_id}-{task_slug}.md", task)
|
|
536
|
+
|
|
537
|
+
update = f"""# Imported from Spec Kit-style artifact
|
|
538
|
+
|
|
539
|
+
Imported `{source_display}` into Delano project `{slug}`.
|
|
540
|
+
|
|
541
|
+
## Source classification
|
|
542
|
+
|
|
543
|
+
- Shape: single-file Spec Kit-style markdown fixture.
|
|
544
|
+
- Confidence: initial supported fixture shape.
|
|
545
|
+
|
|
546
|
+
## Imported counts
|
|
547
|
+
|
|
548
|
+
- User stories: {len(user_stories)}
|
|
549
|
+
- Acceptance scenarios: {len(acceptance)}
|
|
550
|
+
- Functional requirements: {len(requirements)}
|
|
551
|
+
- Non-functional requirements: {len(non_functional)}
|
|
552
|
+
- Clarifications: {len(clarifications)}
|
|
553
|
+
- Tasks: {len(raw_tasks)}
|
|
554
|
+
|
|
555
|
+
## Unresolved clarifications
|
|
556
|
+
|
|
557
|
+
{md_list(clarifications)}
|
|
558
|
+
|
|
559
|
+
## Next step
|
|
560
|
+
|
|
561
|
+
Review the generated project, then run Delano validation and a probe before activation.
|
|
562
|
+
"""
|
|
563
|
+
write_file(project_dir / "updates" / f"{today}-imported-from-spec-kit.md", update)
|
|
564
|
+
PY
|
|
565
|
+
|
|
566
|
+
validation_status="skipped"
|
|
567
|
+
ok="true"
|
|
568
|
+
error=""
|
|
569
|
+
if [[ "$validate" == "true" ]]; then
|
|
570
|
+
if [[ "$json" == "true" ]]; then
|
|
571
|
+
validation_log="$(mktemp)"
|
|
572
|
+
if "$root/.agents/scripts/pm/validate.sh" >"$validation_log" 2>&1; then
|
|
573
|
+
validation_status="passed"
|
|
574
|
+
else
|
|
575
|
+
validation_status="failed"
|
|
576
|
+
ok="false"
|
|
577
|
+
error="validation_failed"
|
|
578
|
+
fi
|
|
579
|
+
rm -f "$validation_log"
|
|
580
|
+
else
|
|
581
|
+
"$root/.agents/scripts/pm/validate.sh"
|
|
582
|
+
validation_status="passed"
|
|
583
|
+
fi
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
if [[ "$json" == "true" ]]; then
|
|
587
|
+
project_json="$(printf '%s' "$project_dir" | json_escape)"
|
|
588
|
+
source_display="$source_md"
|
|
589
|
+
case "$source_display" in
|
|
590
|
+
"$root"/*) source_display="${source_display#"$root/"}" ;;
|
|
591
|
+
esac
|
|
592
|
+
source_json="$(printf '%s' "$source_display" | json_escape)"
|
|
593
|
+
validation_json="$(printf '%s' "$validation_status" | json_escape)"
|
|
594
|
+
if [[ "$ok" == "true" ]]; then
|
|
595
|
+
printf '{"ok":true,"command":"import-spec-kit","project":%s,"source":%s,"validation":%s}\n' "$project_json" "$source_json" "$validation_json"
|
|
596
|
+
else
|
|
597
|
+
error_json="$(printf '%s' "$error" | json_escape)"
|
|
598
|
+
printf '{"ok":false,"command":"import-spec-kit","project":%s,"source":%s,"validation":%s,"error":%s}\n' "$project_json" "$source_json" "$validation_json" "$error_json"
|
|
599
|
+
exit 1
|
|
600
|
+
fi
|
|
601
|
+
else
|
|
602
|
+
echo "Created Delano project from Spec Kit-style artifact: $project_dir"
|
|
603
|
+
echo "Validation: $validation_status"
|
|
604
|
+
echo "Next: review $project_dir/spec.md, then run a probe before activation."
|
|
605
|
+
fi
|
|
@@ -32,7 +32,7 @@ cat > "$project_dir/spec.md" <<SPEC
|
|
|
32
32
|
name: $name
|
|
33
33
|
slug: $slug
|
|
34
34
|
owner: $owner
|
|
35
|
-
status:
|
|
35
|
+
status: planned
|
|
36
36
|
created: $now
|
|
37
37
|
updated: $now
|
|
38
38
|
outcome: <measurable target>
|
|
@@ -49,6 +49,12 @@ probe_status: <pending|skipped|completed>
|
|
|
49
49
|
|
|
50
50
|
## Outcome and Success Metrics
|
|
51
51
|
|
|
52
|
+
## User Stories
|
|
53
|
+
- US-001: As a <user>, I want <capability>, so that <outcome>.
|
|
54
|
+
|
|
55
|
+
## Acceptance Scenarios
|
|
56
|
+
- AC-001: Given <context>, when <action>, then <observable result>.
|
|
57
|
+
|
|
52
58
|
## Scope
|
|
53
59
|
### In Scope
|
|
54
60
|
### Out of Scope
|
|
@@ -57,6 +63,12 @@ probe_status: <pending|skipped|completed>
|
|
|
57
63
|
|
|
58
64
|
## Non-Functional Requirements
|
|
59
65
|
|
|
66
|
+
## Assumptions
|
|
67
|
+
- <assumption to validate>
|
|
68
|
+
|
|
69
|
+
## Needs Clarification
|
|
70
|
+
- <question that must be answered before activation or execution>
|
|
71
|
+
|
|
60
72
|
## Hypotheses and Unknowns
|
|
61
73
|
|
|
62
74
|
## Touchpoints to Exercise
|
|
@@ -81,15 +93,32 @@ created: $now
|
|
|
81
93
|
updated: $now
|
|
82
94
|
linear_project_id:
|
|
83
95
|
risk_level: <low|medium|high>
|
|
84
|
-
spec_status_at_plan_time:
|
|
96
|
+
spec_status_at_plan_time: planned
|
|
85
97
|
---
|
|
86
98
|
|
|
87
99
|
# Delivery Plan: $name
|
|
88
100
|
|
|
89
101
|
## What Changed After Probe
|
|
90
102
|
|
|
103
|
+
## Technical Context
|
|
104
|
+
|
|
91
105
|
## Architecture Decisions
|
|
92
106
|
|
|
107
|
+
## Policy and Contract Checks
|
|
108
|
+
- [ ] `.project` remains the execution source of truth
|
|
109
|
+
- [ ] Probe decision is explicit
|
|
110
|
+
- [ ] Evidence gates are defined before handoff
|
|
111
|
+
- [ ] External sync writes require dry-run or operator approval
|
|
112
|
+
|
|
113
|
+
## Generated Artifact Map
|
|
114
|
+
- `spec.md`: <source or generation notes>
|
|
115
|
+
- `plan.md`: <source or generation notes>
|
|
116
|
+
- `workstreams/`: <source or generation notes>
|
|
117
|
+
- `tasks/`: <source or generation notes>
|
|
118
|
+
|
|
119
|
+
## Complexity Exceptions
|
|
120
|
+
- <exception, rationale, and owner>
|
|
121
|
+
|
|
93
122
|
## Probe-Driven Architecture Changes
|
|
94
123
|
|
|
95
124
|
## Workstream Design
|