@khoavu882/doflow 2.0.1 → 2.0.2
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/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/session-end.sh +21 -1
- package/core/harnesses/codex/hooks/session-end.impl.sh +21 -1
- package/core/harnesses/shared/hooks/policies/compact-text.sh +8 -0
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +25 -1
- package/core/registry/contribution-points.json +1 -1
- package/core/registry/intake.json +3 -3
- package/core/registry/lifecycle.json +256 -5
- package/core/registry/packages/doflow/doflow-package.json +2 -2
- package/core/registry/settings.json +12 -12
- package/core/registry/storage.json +1 -2
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +7 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +12 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +86 -33
- package/core/shared/scripts/doflow/bash/do-review-package.sh +36 -2
- package/core/shared/scripts/doflow/bin/doflow-run +18 -0
- package/core/shared/skills/do-design/SKILL.md +6 -2
- package/core/shared/skills/do-design/references/data_model_example.md +167 -0
- package/core/shared/templates/doflow/data-model-template.md +136 -102
- package/package.json +1 -1
- package/src/adapters/antigravity/index.js +54 -12
- package/src/adapters/codex/agents.js +15 -1
- package/src/adapters/codex/config.js +5 -2
- package/src/adapters/copilot/index.js +11 -4
- package/src/adapters/copy-tree.js +7 -0
- package/src/adapters/created-file.js +41 -0
- package/src/adapters/mcp-entries.js +11 -1
- package/src/adapters/opencode/index.js +32 -5
- package/src/adapters/pi/index.js +15 -4
- package/src/adapters/pi/mcp.js +44 -18
- package/src/cli/commands/remove.js +3 -1
- package/src/cli/commands/update.js +7 -3
- package/src/cli/index.js +24 -12
- package/src/cli/runtime-commands.js +1 -1
- package/src/cli/shared.js +5 -3
- package/src/helper/marker-merge.js +11 -7
- package/src/install/backup-set.js +7 -3
- package/src/install/backup.js +13 -7
- package/src/install/context.js +2 -1
- package/src/install/manifest.js +35 -11
- package/src/install/paths.js +38 -0
- package/src/install/targets.js +11 -2
- package/src/lifecycle/index.js +80 -3
- package/src/lifecycle/retired-harnesses.js +16 -1
- package/src/lifecycle/view.js +3 -2
- package/src/registry/index.js +21 -4
- package/src/release/docs-blocks.js +5 -1
- package/src/runtime/advice/doctor.js +2 -2
- package/src/runtime/brief-tools.js +2 -1
- package/src/runtime/capability-router.js +26 -3
- package/src/runtime/context/pointer.js +17 -5
- package/src/runtime/context-pack.js +48 -8
- package/src/runtime/dispatch/integrate.js +27 -8
- package/src/runtime/dispatch/loop.js +7 -0
- package/src/runtime/dispatch/verify-run.js +9 -3
- package/src/runtime/feature-pointer.js +10 -1
- package/src/runtime/health.js +9 -4
- package/src/runtime/implementation-gate.js +10 -2
- package/src/runtime/intake/engine.js +11 -9
- package/src/runtime/knowledge/code-retrieval.js +11 -5
- package/src/runtime/knowledge/semble-provider.js +12 -2
- package/src/runtime/lifecycle/cli.js +7 -5
- package/src/runtime/lifecycle/overview.js +32 -2
- package/src/runtime/lifecycle/release.js +16 -3
- package/src/runtime/lifecycle/retention.js +12 -15
- package/src/runtime/lifecycle/store-upkeep.js +13 -3
- package/src/runtime/mask.js +3 -3
- package/src/runtime/mcp-consultations.js +11 -5
- package/src/runtime/research-request.js +3 -1
- package/src/runtime/review/command.js +3 -7
- package/src/runtime/review/start.js +24 -10
- package/src/runtime/roadmap/loop.js +14 -3
- package/src/runtime/roadmap/rows.js +2 -1
- package/src/runtime/settings/files.js +3 -1
- package/src/runtime/settings/prepared-run.js +12 -2
- package/src/runtime/settings/resolve.js +13 -1
- package/src/runtime/settings/write.js +73 -2
- package/src/runtime/storage/registry.js +2 -2
- package/src/runtime/storage/roots.js +4 -18
- package/src/runtime/storage/rules.js +22 -46
- package/src/runtime/storage/work-status.js +14 -4
- package/src/runtime/task-scope.js +17 -2
- package/src/runtime/task-state.js +11 -4
- package/src/runtime/trace/analyze.js +4 -2
- package/src/runtime/trace/indicators.js +25 -20
- package/src/runtime/trace/spans.js +4 -5
- package/src/runtime/verb-view.js +6 -5
- package/src/runtime/worker/command.js +4 -2
- package/src/runtime/worker/readiness.js +8 -4
- package/src/runtime/worker/supervisor.js +18 -6
- package/src/runtime/workflow-orchestrator.js +6 -2
|
@@ -111,10 +111,14 @@ is_dirty() {
|
|
|
111
111
|
# means anything. The local branch was used unconditionally, so a develop that had not been pulled
|
|
112
112
|
# made every distance wrong by however stale it was — observed live at 22 reported against 32.
|
|
113
113
|
#
|
|
114
|
-
# The remote-tracking ref is preferred where it exists
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
114
|
+
# The remote-tracking ref is preferred where it exists, unless the local branch is strictly ahead
|
|
115
|
+
# of it (origin/<branch> is an ancestor of the branch and they differ): then the local branch holds
|
|
116
|
+
# integrated work the remote has not seen yet, and measuring from origin would count that work as
|
|
117
|
+
# the caller's own. An equal, behind or diverged local branch keeps the remote preference; the local
|
|
118
|
+
# branch is the fallback for a repository with no remote or one never fetched. Preferring the remote
|
|
119
|
+
# does not make the answer *fresh* — origin/develop is only as current as the last fetch — so the
|
|
120
|
+
# third field reports how far the local branch trails, which is non-zero exactly when a `git fetch`
|
|
121
|
+
# would change the answer.
|
|
118
122
|
#
|
|
119
123
|
# Echoes: "<ref> <kind> <local-behind-remote>"
|
|
120
124
|
resolve_integration_ref() {
|
|
@@ -125,6 +129,10 @@ resolve_integration_ref() {
|
|
|
125
129
|
kind="remote-tracking"
|
|
126
130
|
if git rev-parse --verify --quiet "refs/heads/${branch}" >/dev/null 2>&1; then
|
|
127
131
|
behind="$(git rev-list --count "${branch}..origin/${branch}" 2>/dev/null || echo 0)"
|
|
132
|
+
if [ "$behind" = "0" ] && [ "$(git rev-list --count "origin/${branch}..${branch}" 2>/dev/null || echo 0)" -gt 0 ]; then
|
|
133
|
+
ref="$branch"
|
|
134
|
+
kind="local"
|
|
135
|
+
fi
|
|
128
136
|
fi
|
|
129
137
|
fi
|
|
130
138
|
printf '%s %s %s\n' "$ref" "$kind" "$behind"
|
|
@@ -12,6 +12,10 @@ case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; th
|
|
|
12
12
|
# reducing dispatch overhead. The script forms groups from `owner:` fields, computes union
|
|
13
13
|
# file sets per group, and reports overlaps across groups (`group_overlaps[]`).
|
|
14
14
|
#
|
|
15
|
+
# Owner and files are read from the whole task block: the task line and a `Verify:` line under it. A task
|
|
16
|
+
# that declares no `files:` (`files: none` is a declaration), or declares different sets, is named in
|
|
17
|
+
# `files_missing[]` / `files_conflict[]`, and while one exists `parallel_safe` is `null`, never `true`.
|
|
18
|
+
#
|
|
15
19
|
# Advisory by design: it reports overlaps and grouping decisions; the calling skill decides
|
|
16
20
|
# to serialize or group dispatch. It never edits the plan and never dispatches anything.
|
|
17
21
|
#
|
|
@@ -67,7 +71,70 @@ plan_abs="$repo_root/$plan_rel"
|
|
|
67
71
|
|
|
68
72
|
# Pipeline: awk outputs tab-separated values (one per line), jq reads raw lines as strings,
|
|
69
73
|
# aggregates them into an array, then processes each row.
|
|
74
|
+
#
|
|
75
|
+
# A task is its whole block: the `- [ ] <id>` line and the indented lines under it, up to the next task or
|
|
76
|
+
# heading. `owner:` and `files:` are read from the task line and from a `Verify:` line (the layout plans
|
|
77
|
+
# written with a verification line use); each is the text after the line's first em dash. A task that
|
|
78
|
+
# declares files twice must declare the same set twice: a different set is `conflict`, and a task that
|
|
79
|
+
# declares none is `missing`. `files: none` is a declaration of an empty set, not a missing one.
|
|
70
80
|
awk -v want="$phase" '
|
|
81
|
+
# Sorted, de-duplicated, comma-joined form of a comma list, so two spellings of one set compare equal.
|
|
82
|
+
function normalize(list, n, parts, i, j, t, out, last) {
|
|
83
|
+
n = split(list, parts, ",")
|
|
84
|
+
for (i = 1; i <= n; i++) { gsub(/^[ \t]+|[ \t]+$/, "", parts[i]) }
|
|
85
|
+
for (i = 2; i <= n; i++) {
|
|
86
|
+
t = parts[i]
|
|
87
|
+
for (j = i - 1; j >= 1 && parts[j] > t; j--) parts[j + 1] = parts[j]
|
|
88
|
+
parts[j + 1] = t
|
|
89
|
+
}
|
|
90
|
+
out = ""; last = "\001"
|
|
91
|
+
for (i = 1; i <= n; i++) {
|
|
92
|
+
if (parts[i] == "" || parts[i] ~ /^none/ || parts[i] == last) continue
|
|
93
|
+
out = out (out == "" ? "" : ",") parts[i]
|
|
94
|
+
last = parts[i]
|
|
95
|
+
}
|
|
96
|
+
return out
|
|
97
|
+
}
|
|
98
|
+
# Reads one line'"'"'s owner:/files: fields into the current task.
|
|
99
|
+
function declare(line, dash_pos, section, o, f) {
|
|
100
|
+
dash_pos = index(line, "—")
|
|
101
|
+
if (dash_pos == 0) return
|
|
102
|
+
section = substr(line, dash_pos + length("—"))
|
|
103
|
+
if (match(section, /owner:[^;]*/)) {
|
|
104
|
+
o = substr(section, RSTART + 7, RLENGTH - 7)
|
|
105
|
+
gsub(/^[ \t]+|[ \t]+$/, "", o)
|
|
106
|
+
if (o != "") {
|
|
107
|
+
if (owner == "") owner = o
|
|
108
|
+
else if (owner != o) conflict = 1
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (match(section, /files:[^;]*/)) {
|
|
112
|
+
f = substr(section, RSTART + 6, RLENGTH - 6)
|
|
113
|
+
gsub(/^[ \t]+|[ \t]+$/, "", f)
|
|
114
|
+
if (f == "") return
|
|
115
|
+
declared++
|
|
116
|
+
set = normalize(f)
|
|
117
|
+
if (declared == 1) first = set
|
|
118
|
+
else if (set != first) conflict = 1
|
|
119
|
+
m = split(set, items, ",")
|
|
120
|
+
for (k = 1; k <= m; k++) if (items[k] != "" && !(items[k] in seen)) { seen[items[k]] = 1; order[++norder] = items[k] }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function flush( status, k) {
|
|
124
|
+
if (id == "") return
|
|
125
|
+
status = conflict ? "conflict" : (declared == 0 ? "missing" : "ok")
|
|
126
|
+
if (norder == 0) print id "\t" par "\t" owner "\t\t" status
|
|
127
|
+
for (k = 1; k <= norder; k++) print id "\t" par "\t" owner "\t" order[k] "\t" status
|
|
128
|
+
id = ""
|
|
129
|
+
}
|
|
130
|
+
function start(line) {
|
|
131
|
+
id = substr(line, RSTART + 6, RLENGTH - 6)
|
|
132
|
+
par = (line ~ /^- \[[ x]\] [^ ]+ \[P\]/) ? "P" : "S"
|
|
133
|
+
owner = ""; declared = 0; conflict = 0; first = ""; norder = 0
|
|
134
|
+
delete seen; delete order
|
|
135
|
+
declare(line)
|
|
136
|
+
}
|
|
137
|
+
/^#+ / { flush() }
|
|
71
138
|
/^### Phase / {
|
|
72
139
|
inphase = ($3 == want)
|
|
73
140
|
next
|
|
@@ -75,43 +142,22 @@ awk -v want="$phase" '
|
|
|
75
142
|
/^### / { inphase = 0; next }
|
|
76
143
|
!inphase { next }
|
|
77
144
|
/^- \[[ x]\] / {
|
|
78
|
-
|
|
79
|
-
if (match(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# Extract only the portion after em-dash for owner:/files: parsing
|
|
83
|
-
dash_pos = index(line, "—")
|
|
84
|
-
if (dash_pos == 0) {
|
|
85
|
-
print id "\t" par "\t\t"
|
|
86
|
-
next
|
|
87
|
-
}
|
|
88
|
-
field_section = substr(line, dash_pos + length("—"))
|
|
89
|
-
owner = ""
|
|
90
|
-
if (match(field_section, /owner:[^;]*/)) {
|
|
91
|
-
owner = substr(field_section, RSTART + 7, RLENGTH - 7)
|
|
92
|
-
gsub(/^[ \t]+|[ \t]+$/, "", owner)
|
|
93
|
-
}
|
|
94
|
-
if (match(field_section, /files:[^;]*/)) {
|
|
95
|
-
files = substr(field_section, RSTART + 6, RLENGTH - 6)
|
|
96
|
-
n = split(files, parts, ",")
|
|
97
|
-
emitted = 0
|
|
98
|
-
for (i = 1; i <= n; i++) {
|
|
99
|
-
f = parts[i]
|
|
100
|
-
gsub(/^[ \t]+|[ \t]+$/, "", f)
|
|
101
|
-
if (f == "" || f ~ /^none/) continue
|
|
102
|
-
print id "\t" par "\t" owner "\t" f
|
|
103
|
-
emitted = 1
|
|
104
|
-
}
|
|
105
|
-
if (!emitted) print id "\t" par "\t" owner "\t"
|
|
106
|
-
} else {
|
|
107
|
-
print id "\t" par "\t" owner "\t"
|
|
108
|
-
}
|
|
145
|
+
flush()
|
|
146
|
+
if (match($0, /^- \[[ x]\] [^ ]+/) == 0) next
|
|
147
|
+
start($0)
|
|
148
|
+
next
|
|
109
149
|
}
|
|
150
|
+
id != "" && /^[ \t]*[-*]?[ \t]*\**Verify\**:/ { declare($0) }
|
|
151
|
+
END { flush() }
|
|
110
152
|
' "$plan_abs" | jq -R . | jq -s --arg phase "$phase" '
|
|
111
153
|
# Convert array of tab-separated strings to array of objects
|
|
112
154
|
map(split("\t")) |
|
|
113
|
-
map({id: .[0], par: .[1], owner: (.[2] // ""), file: (.[3] // "")}) |
|
|
155
|
+
map({id: .[0], par: .[1], owner: (.[2] // ""), file: (.[3] // ""), meta: (.[4] // "missing")}) |
|
|
114
156
|
. as $rows |
|
|
157
|
+
|
|
158
|
+
# Tasks whose file metadata cannot establish anything: none declared, or declarations that disagree
|
|
159
|
+
([.[] | select(.meta == "missing") | .id] | unique) as $files_missing |
|
|
160
|
+
([.[] | select(.meta == "conflict") | .id] | unique) as $files_conflict |
|
|
115
161
|
|
|
116
162
|
# Separate by parallel/sequential
|
|
117
163
|
([.[] | select(.par == "P") | .id] | unique) as $par |
|
|
@@ -200,8 +246,15 @@ awk -v want="$phase" '
|
|
|
200
246
|
# a non-empty `group_overlaps` naming the very file two groups both write. `do-execute-plan`
|
|
201
247
|
# tells the reader to branch on this field and never on the exit code, so either shape
|
|
202
248
|
# authorises concurrent writes to one file.
|
|
249
|
+
#
|
|
250
|
+
# A task with no `files:` (or two that disagree) has an unknown write set, so a phase containing one
|
|
251
|
+
# is never reported safe: `null` (unknown, run serially) unless a real overlap already makes it `false`.
|
|
203
252
|
parallel_safe: (if (($par | length) == 0 and ($seq | length) == 0) then null
|
|
204
|
-
|
|
253
|
+
elif (($overlaps | length) > 0 or ($final_group_overlaps | length) > 0) then false
|
|
254
|
+
elif (($files_missing | length) > 0 or ($files_conflict | length) > 0) then null
|
|
255
|
+
else true end),
|
|
256
|
+
files_missing: $files_missing,
|
|
257
|
+
files_conflict: $files_conflict,
|
|
205
258
|
serialize: ([$overlaps[] | .tasks[]] | unique),
|
|
206
259
|
groups: $final_groups,
|
|
207
260
|
group_overlaps: $final_group_overlaps,
|
|
@@ -21,7 +21,10 @@ case "${OSTYPE:-}" in msys*|cygwin*|win32*) if command -v jq >/dev/null 2>&1; th
|
|
|
21
21
|
# Usage:
|
|
22
22
|
# do-review-package.sh --task=<id> --base=<sha> --head=<sha> [--slug=<slug>] [--json]
|
|
23
23
|
#
|
|
24
|
-
# Emits: {path, commits, bytes, base, head}
|
|
24
|
+
# Emits: {path, commits, bytes, base, head}, plus missing[] when the rules block could not be built.
|
|
25
|
+
#
|
|
26
|
+
# Between the title and the commit list the package carries the "Rules that apply to you" block: the
|
|
27
|
+
# mustCarry lines of the rule ledger (core/registry/rules.json), as the task and group briefs print them.
|
|
25
28
|
|
|
26
29
|
set -uo pipefail
|
|
27
30
|
|
|
@@ -96,10 +99,40 @@ if [ "${commits:-0}" -eq 0 ]; then
|
|
|
96
99
|
exit 2
|
|
97
100
|
fi
|
|
98
101
|
|
|
102
|
+
# The registry file `core/registry/<name>` of the runtime this script belongs to: next to the checkout or
|
|
103
|
+
# projected runtime found above this script, else the global projected runtime (as do-task-brief.sh).
|
|
104
|
+
registry_file() { # registry_file <name>
|
|
105
|
+
local d="$script_dir" c
|
|
106
|
+
while [ -n "$d" ] && [ "$d" != "/" ]; do
|
|
107
|
+
for c in "$d/core/registry/$1" "$d/runtime/core/registry/$1"; do
|
|
108
|
+
if [ -f "$c" ]; then printf '%s' "$c"; return 0; fi
|
|
109
|
+
done
|
|
110
|
+
d="$(dirname "$d")"
|
|
111
|
+
done
|
|
112
|
+
c="${HOME:-}/.doflow/runtime/core/registry/$1"
|
|
113
|
+
if [ -f "$c" ]; then printf '%s' "$c"; fi
|
|
114
|
+
return 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
missing='[]'
|
|
118
|
+
rules_block=""
|
|
119
|
+
rules_file="$(registry_file rules.json)"
|
|
120
|
+
if [ -z "$rules_file" ]; then
|
|
121
|
+
missing='["brief-rules: core/registry/rules.json is missing"]'
|
|
122
|
+
else
|
|
123
|
+
rule_lines="$(jq -r 'if (.mustCarry | type) == "array" and (.mustCarry | length) > 0 and (.mustCarry | all(type == "string")) then .mustCarry[] | "- " + . else empty end' "$rules_file" 2>/dev/null)"
|
|
124
|
+
if [ -z "$rule_lines" ]; then
|
|
125
|
+
missing='["brief-rules: core/registry/rules.json has no mustCarry array"]'
|
|
126
|
+
else
|
|
127
|
+
rules_block="$(printf '## Rules that apply to you\n\n%s\n' "$rule_lines")"
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
|
|
99
131
|
write_ok=1
|
|
100
132
|
{
|
|
101
133
|
echo "# Review package: $base7..$head7"
|
|
102
134
|
echo
|
|
135
|
+
if [ -n "$rules_block" ]; then printf '%s\n\n' "$rules_block"; fi
|
|
103
136
|
echo "## Commits"
|
|
104
137
|
git log --oneline "$base..$head_ref"
|
|
105
138
|
echo
|
|
@@ -127,5 +160,6 @@ jq -n \
|
|
|
127
160
|
--argjson commits "${commits:-0}" \
|
|
128
161
|
--argjson bytes "${bytes:-0}" \
|
|
129
162
|
--arg base "$base7" --arg head "$head7" \
|
|
130
|
-
|
|
163
|
+
--argjson missing "$missing" \
|
|
164
|
+
'{path:$path, commits:$commits, bytes:$bytes, base:$base, head:$head} + (if ($missing | length) > 0 then {missing:$missing} else {} end)'
|
|
131
165
|
exit 0
|
|
@@ -339,6 +339,23 @@ resolve_node_cli() {
|
|
|
339
339
|
return 1
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
# A checkout found from the caller's cwd may be older than its local develop branch. Keep this
|
|
343
|
+
# advisory on stderr so the selected CLI's stdout and exit status remain untouched.
|
|
344
|
+
warn_checkout_pwd_behind_develop() {
|
|
345
|
+
local cwd head counts head_only develop_only
|
|
346
|
+
[ "$NODE_CLI_SOURCE" = "checkout-pwd" ] || return 0
|
|
347
|
+
cwd="$(pwd -P 2>/dev/null || pwd)"
|
|
348
|
+
head="$(git -C "$cwd" rev-parse --verify HEAD 2>/dev/null)" || return 0
|
|
349
|
+
git -C "$cwd" show-ref --verify --quiet refs/heads/develop || return 0
|
|
350
|
+
counts="$(git -C "$cwd" rev-list --left-right --count HEAD...refs/heads/develop 2>/dev/null)" || return 0
|
|
351
|
+
read -r head_only develop_only <<EOF
|
|
352
|
+
$counts
|
|
353
|
+
EOF
|
|
354
|
+
case "$head_only:$develop_only" in
|
|
355
|
+
0:[1-9]* ) printf 'doflow-run: warning: selected runtime %s at %s is behind local develop\n' "$NODE_CLI_PATH" "$head" >&2 ;;
|
|
356
|
+
esac
|
|
357
|
+
}
|
|
358
|
+
|
|
342
359
|
# ---------------------------------------------------------------------------------- reporting
|
|
343
360
|
|
|
344
361
|
wants_json=false
|
|
@@ -585,6 +602,7 @@ dispatch_node() {
|
|
|
585
602
|
fail 2 "cli-not-found" \
|
|
586
603
|
"verb '$verb' is served by the Node CLI, which could not be located; set DOFLOW_CLI to a doflow.js path, install the package (npm i -g @khoavu882/doflow), or run from a checkout"
|
|
587
604
|
fi
|
|
605
|
+
warn_checkout_pwd_behind_develop
|
|
588
606
|
if [ "$NODE_CLI_KIND" = "script" ]; then
|
|
589
607
|
command -v node >/dev/null 2>&1 || fail 2 "node-not-found" \
|
|
590
608
|
"verb '$verb' needs node on PATH to run $NODE_CLI_PATH"
|
|
@@ -136,6 +136,10 @@ a `design.md` for it invents an artifact its workflow never reads.
|
|
|
136
136
|
create one: fill Data Model and Sequence / Data Flow in `design.md` — the content
|
|
137
137
|
`specs.md`'s former §2 and the template's old §6 carried — exactly as the old two-file
|
|
138
138
|
convention did, and leave that dir's layout alone — this feature migrates nothing.
|
|
139
|
+
Otherwise fill `data-model.md` §1 to §9. Read `references/data_model_example.md` only while filling it.
|
|
140
|
+
Each table row ends in a Source tag; copy every `proposed` row to `design.md` Open Points for
|
|
141
|
+
Review. `design.md` Journeys keep user-visible states only and link to `./data-model.md` §3 for
|
|
142
|
+
the entity states.
|
|
139
143
|
The template is `templates/doflow/design-template.md` in the install step 1 resolved: take `constitution_base` from that JSON and swap its trailing `guidance/references/CONSTITUTION_BASE.md` for that path.
|
|
140
144
|
Fill `[REQUIREMENT_PATH]` with step 1's own resolved `requirement` field, verbatim
|
|
141
145
|
(repo-root-relative, exactly as the resolver returns it) — never hand-compute it, which is only
|
|
@@ -167,8 +171,8 @@ Filling the C4 levels, read the guidance tree's `references/DIAGRAM_GUIDE.md` fi
|
|
|
167
171
|
```
|
|
168
172
|
Run it a second time against the specs path when step 6 wrote one — `specs.md`'s §1 Interface
|
|
169
173
|
Contracts is an indexed section, so it is checked the same way. Run it a third time against the
|
|
170
|
-
data-model path when step 6 wrote one — `data-model.md`'s §
|
|
171
|
-
checked the same way, even though its §1
|
|
174
|
+
data-model path when step 6 wrote one — `data-model.md`'s §9 History is its one indexed section,
|
|
175
|
+
checked the same way, even though its §1-§8 stay free-form. Surface findings verbatim; a
|
|
172
176
|
non-zero exit is advisory and does not halt the chain.
|
|
173
177
|
8. **Batch this stage's evidence** — one pass here at the stage boundary, never one call per fact.
|
|
174
178
|
`<task id>` is the unit these stores key on: the plan task id once `plan.md` exists, otherwise
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Data Model: Order checkout (worked example)
|
|
2
|
+
|
|
3
|
+
**Feature:** 000-order-checkout · **Design:** ./design.md · **Maturity:** Draft · **Created:** 2025-01-01
|
|
4
|
+
|
|
5
|
+
> Worked example of a filled `data-model.md`. Read it only while filling the template; the domain
|
|
6
|
+
> is invented. FR-001, FR-002, NFR-001 and IC-001 stand for requirement and contract IDs of that feature.
|
|
7
|
+
|
|
8
|
+
## 1. Data Model
|
|
9
|
+
|
|
10
|
+
### Domain inventory
|
|
11
|
+
|
|
12
|
+
| Domain | Entities | Stateful | Owner | Source |
|
|
13
|
+
|---|---|---|---|---|
|
|
14
|
+
| Ordering | Order | yes | order-service | FR-001 |
|
|
15
|
+
| Ordering | OrderLine | no | order-service | FR-001 |
|
|
16
|
+
|
|
17
|
+
### Conceptual domain map
|
|
18
|
+
|
|
19
|
+
```mermaid
|
|
20
|
+
flowchart LR
|
|
21
|
+
O["Order"] -->|"contains"| L["OrderLine"]
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
#### ER view: ordering
|
|
25
|
+
|
|
26
|
+
```mermaid
|
|
27
|
+
erDiagram
|
|
28
|
+
ORDER ||--|{ ORDERLINE : "contains"
|
|
29
|
+
ORDER {
|
|
30
|
+
uuid id PK
|
|
31
|
+
string status
|
|
32
|
+
timestamp placed_at
|
|
33
|
+
}
|
|
34
|
+
ORDERLINE {
|
|
35
|
+
uuid id PK
|
|
36
|
+
uuid order_id FK
|
|
37
|
+
int quantity
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Data dictionary
|
|
42
|
+
|
|
43
|
+
#### Dictionary: Order
|
|
44
|
+
|
|
45
|
+
| Field | Type | Null | Rule | PII class | Source |
|
|
46
|
+
|---|---|---|---|---|---|
|
|
47
|
+
| id | uuid | no | generated once | none | code:src/orders/order.ts:12 |
|
|
48
|
+
| status | string | no | one of New, Paid, Shipped, Closed | none | FR-001 |
|
|
49
|
+
| placed_at | timestamp | no | set at creation | none | proposed |
|
|
50
|
+
|
|
51
|
+
#### Dictionary: OrderLine
|
|
52
|
+
|
|
53
|
+
| Field | Type | Null | Rule | PII class | Source |
|
|
54
|
+
|---|---|---|---|---|---|
|
|
55
|
+
| id | uuid | no | generated once | none | code:src/orders/line.ts:8 |
|
|
56
|
+
| order_id | uuid | no | references Order.id | none | FR-001 |
|
|
57
|
+
| quantity | int | no | at least 1 | none | FR-002 |
|
|
58
|
+
|
|
59
|
+
### Database Schemas (ORM / DDL)
|
|
60
|
+
|
|
61
|
+
| Table | Purpose | Primary Key | Foreign Keys & Relations | Key Indexes | Schema / ORM File |
|
|
62
|
+
|---|---|---|---|---|---|
|
|
63
|
+
| `orders` | One customer order | `id` | none | `status` | src/orders/order.ts |
|
|
64
|
+
| `order_lines` | Items of an order | `id` | `order_id` → `orders(id)` | `order_id` | src/orders/line.ts |
|
|
65
|
+
|
|
66
|
+
### UX / UI Specifications
|
|
67
|
+
- **Design Tokens & Cues:** status badge per state (New, Paid, Shipped, Closed)
|
|
68
|
+
- **Component States:** loading, empty cart, populated, payment error
|
|
69
|
+
|
|
70
|
+
## 2. Sequence / Data Flow
|
|
71
|
+
|
|
72
|
+
### Interaction: place and pay
|
|
73
|
+
|
|
74
|
+
A shopper submits a cart; the flow ends with a paid order or a refused payment.
|
|
75
|
+
|
|
76
|
+
```mermaid
|
|
77
|
+
sequenceDiagram
|
|
78
|
+
actor Shopper
|
|
79
|
+
participant SVC as order-service
|
|
80
|
+
participant DB as orders-db
|
|
81
|
+
|
|
82
|
+
Shopper->>SVC: submit cart
|
|
83
|
+
SVC->>DB: insert Order (New)
|
|
84
|
+
alt payment refused
|
|
85
|
+
SVC-->>Shopper: payment error
|
|
86
|
+
else payment accepted
|
|
87
|
+
SVC->>DB: update Order (Paid)
|
|
88
|
+
SVC-->>Shopper: confirmation
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Swimlane flows
|
|
93
|
+
|
|
94
|
+
#### Flow: place-order
|
|
95
|
+
|
|
96
|
+

|
|
97
|
+
|
|
98
|
+
A shopper submits a cart; the flow ends with a paid order handed to fulfilment.
|
|
99
|
+
|
|
100
|
+
## 3. Lifecycles
|
|
101
|
+
|
|
102
|
+
> **Authoritative:** this section governs the states of Order and the moves between them.
|
|
103
|
+
|
|
104
|
+
#### Lifecycle: Order
|
|
105
|
+
|
|
106
|
+
```mermaid
|
|
107
|
+
stateDiagram-v2
|
|
108
|
+
[*] --> New
|
|
109
|
+
New --> Paid: pay
|
|
110
|
+
New --> Closed: cancel
|
|
111
|
+
Paid --> Shipped: ship
|
|
112
|
+
Shipped --> Closed: confirm delivery
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Transitions**
|
|
116
|
+
|
|
117
|
+
| From | Event | Guard | Actor | Side effect | To | Emitted event | Source |
|
|
118
|
+
|---|---|---|---|---|---|---|---|
|
|
119
|
+
| New | pay | payment accepted | Shopper | stock reserved | Paid | OrderPaid | FR-001 |
|
|
120
|
+
| New | cancel | none | Shopper | none | Closed | OrderClosed | proposed |
|
|
121
|
+
| Paid | ship | stock picked | Warehouse | tracking number set | Shipped | OrderShipped | IC-001 |
|
|
122
|
+
| Shipped | confirm delivery | carrier scan | System | none | Closed | OrderClosed | UNKNOWN -> Open Point |
|
|
123
|
+
|
|
124
|
+
**Illegal transitions**
|
|
125
|
+
|
|
126
|
+
- Closed → any state: a closed order is final.
|
|
127
|
+
- Shipped → Paid: payment cannot be undone after dispatch.
|
|
128
|
+
|
|
129
|
+
**Terminal states:** Closed
|
|
130
|
+
|
|
131
|
+
#### Lifecycle: OrderLine
|
|
132
|
+
|
|
133
|
+
N/A: stateless, a line is fixed when the order is placed and follows its Order.
|
|
134
|
+
|
|
135
|
+
## 4. Cross-entity coupling
|
|
136
|
+
|
|
137
|
+
N/A: one actor-owned entity changes state; OrderLine has no state to couple.
|
|
138
|
+
|
|
139
|
+
## 5. Invariants
|
|
140
|
+
|
|
141
|
+
| Invariant | Holds over | Enforced by | Source |
|
|
142
|
+
|---|---|---|---|
|
|
143
|
+
| An Order has at least one OrderLine | Order, OrderLine | check at placement | FR-002 |
|
|
144
|
+
| A Closed order never changes | Order | code path rejects updates | NFR-001 |
|
|
145
|
+
|
|
146
|
+
## 6. Ownership / CRUD matrix
|
|
147
|
+
|
|
148
|
+
| Entity | Owner | Create | Read | Update | Delete | Source |
|
|
149
|
+
|---|---|---|---|---|---|---|
|
|
150
|
+
| Order | order-service | Shopper | Shopper, Warehouse | order-service | none | IC-001 |
|
|
151
|
+
| OrderLine | order-service | Shopper | Shopper, Warehouse | none | none | proposed |
|
|
152
|
+
|
|
153
|
+
## 7. Event catalogue
|
|
154
|
+
|
|
155
|
+
| Event | Emitted by | Payload | Consumers | Source |
|
|
156
|
+
|---|---|---|---|---|
|
|
157
|
+
| OrderPaid | order-service | order id, total | warehouse | IC-001 |
|
|
158
|
+
| OrderShipped | order-service | order id, tracking number | notifier | proposed |
|
|
159
|
+
| OrderClosed | order-service | order id, reason | reporting | proposed |
|
|
160
|
+
|
|
161
|
+
## 8. Retention, PII and migration
|
|
162
|
+
|
|
163
|
+
N/A: no personal data is stored in these entities and the tables are new.
|
|
164
|
+
|
|
165
|
+
## 9. History
|
|
166
|
+
|
|
167
|
+
None — initial version.
|