@debonet/idd 0.0.0-stage → 0.1.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.
- package/INSTRUCTIONS.md +141 -0
- package/README.md +89 -2
- package/bin/idd +16 -0
- package/bin/idd-merge-hook +13 -0
- package/docs/existing-page-migration.md +92 -0
- package/docs/hosted-viewer.md +164 -0
- package/package.json +47 -4
- package/scripts/check.js +538 -0
- package/scripts/commands.js +847 -0
- package/scripts/html-text.js +50 -0
- package/scripts/import-page.js +80 -0
- package/scripts/merge-hook.js +365 -0
- package/scripts/page-build.js +21 -0
- package/scripts/project-lock.js +135 -0
- package/specs/adoption.md +133 -0
- package/specs/build.md +176 -0
- package/specs/guidance/agent-merge.md +63 -0
- package/specs/guidance/answers.md +108 -0
- package/specs/guidance/claude-code.md +76 -0
- package/specs/guidance/codex.md +85 -0
- package/specs/guidance/session.md +223 -0
- package/specs/guidance/writing-questions.md +171 -0
- package/specs/hosted-questionnaire.md +738 -0
- package/specs/idd-merge-hook.md +42 -0
- package/specs/idd-page.md +340 -0
- package/specs/project-files.md +188 -0
- package/specs/questions-page.md +169 -0
- package/specs/themes.md +94 -0
- package/templates/questions-page.html +337 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# idd-merge-hook
|
|
2
|
+
|
|
3
|
+
Statement ids: complete
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The Git hook command that makes agents reconcile project questionnaire
|
|
8
|
+
branches before completing a code-branch merge.
|
|
9
|
+
|
|
10
|
+
## API
|
|
11
|
+
|
|
12
|
+
* {#s-e4kj5b <arch} `npm exec -- idd-merge-hook install` writes executable IDD
|
|
13
|
+
hooks to `.githooks/` and sets the clone's `core.hooksPath` to that
|
|
14
|
+
directory. The hooks invoke the package through `npm exec -- idd-merge-hook`.
|
|
15
|
+
In a Concord project, its pre-commit hook also runs the Concord spec gate.
|
|
16
|
+
It is repeatable and refuses a different configured hook directory or
|
|
17
|
+
existing default hooks that it cannot preserve.
|
|
18
|
+
* {#s-t0r5nn <arch} `idd-merge-hook check` exits 0 only when the configured hook
|
|
19
|
+
directory and all required executable hook files and commands are present.
|
|
20
|
+
* {#s-sjb7rh <arch} `idd-merge-hook reviewed` can run only during a code merge;
|
|
21
|
+
it fetches `origin` and records a review only when the source IDD branch is
|
|
22
|
+
an ancestor of the target IDD branch.
|
|
23
|
+
* {#s-02z196 <arch} A review record binds the source and target code commits,
|
|
24
|
+
source and target code branch names, and both IDD branch commits. A later
|
|
25
|
+
change to any bound commit invalidates that review.
|
|
26
|
+
* {#s-sj7f9n <arch} During an active code merge, `pre-commit` and
|
|
27
|
+
`pre-merge-commit` fetch `origin` and exit 0 only when no project
|
|
28
|
+
questionnaire branches exist or a matching review record covers an IDD
|
|
29
|
+
source history already merged into the target and the local IDD branches
|
|
30
|
+
have no unpublished commits. Otherwise they print the source and target
|
|
31
|
+
branches and the required IDD reconciliation steps, then exit 1. Outside a
|
|
32
|
+
code merge they exit 0 without fetching.
|
|
33
|
+
* {#s-wjofsg <arch} After a code merge, `post-merge` fetches `origin`, verifies
|
|
34
|
+
that the reviewed source IDD history remains in the target IDD branch, and
|
|
35
|
+
removes the review record. It exits unsuccessfully if the review or
|
|
36
|
+
reconciled history no longer matches.
|
|
37
|
+
|
|
38
|
+
## Dependencies
|
|
39
|
+
|
|
40
|
+
* {#p-cqaul6 >specs/guidance/agent-merge.md} The agent's semantic reconciliation
|
|
41
|
+
process for duplicate, obsolete, or conflicting questions.
|
|
42
|
+
* {#p-gpf7kp >specs/build.md} The package command and repository hook setup.
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# idd
|
|
2
|
+
|
|
3
|
+
Statement ids: complete
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The npm-installed command that prints IDD's workflow, creates a project's
|
|
8
|
+
page file and question file, checks them, builds the questions page from
|
|
9
|
+
them, adds questions with branch-scoped hash ids, records and clears answers
|
|
10
|
+
in the project's answer and pending answer files, and imports questions
|
|
11
|
+
from a page made from the older template; it also reports session status,
|
|
12
|
+
prints a hosted viewer link, and counts questions on an IDD branch.
|
|
13
|
+
|
|
14
|
+
## His words
|
|
15
|
+
|
|
16
|
+
> {#m-f2iwx9} From his instructions file `~/.claude/CLAUDE.md`, section
|
|
17
|
+
> "Questions for me go on a web questions page", as read 2026-11-04: "Every
|
|
18
|
+
> time a question is added: insert the question object before the closing
|
|
19
|
+
> `];` of `vaQuestion`, republish to the same URL, and open the page in my
|
|
20
|
+
> local Chrome"
|
|
21
|
+
|
|
22
|
+
> {#m-xoqfn9} From this session, 2026-11-11: “we should offer the agents tools to
|
|
23
|
+
> quickly get a count of conflicts/unanswered/answered/suspended questions.
|
|
24
|
+
> It should by default pull the current idd-<branch> to make sure its synched.
|
|
25
|
+
> It can have a --nosync option to turn that off. And flags for returning just
|
|
26
|
+
> one of the counts, e.g. --type {c,u,a,s,conflicts,unanswered,answered,suspended}.”
|
|
27
|
+
|
|
28
|
+
> {#m-xhv3ci} In answer to the IDD question “What should the suspended count
|
|
29
|
+
> measure?”, he wrote: “I meant \"superseded\" not \"suspended\"”
|
|
30
|
+
|
|
31
|
+
## API
|
|
32
|
+
|
|
33
|
+
* {#s-4lt6mo <m-f2iwx9@98efad1} **One command.** The command is `idd
|
|
34
|
+
<subcommand> [arguments]`, installed by the public `@debonet/idd` npm
|
|
35
|
+
package and run from the IDD data worktree; {#s-s1eg2z <arch} it reads and
|
|
36
|
+
writes every project path relative to that worktree, never writes outside
|
|
37
|
+
it, and finds its own resources relative to its installed package.
|
|
38
|
+
* {#s-hsle36 <arch} **`guide`** prints the package's current canonical
|
|
39
|
+
adoption and session instructions to stdout and exits 0 without requiring
|
|
40
|
+
a project page or changing files.
|
|
41
|
+
* {#s-7wfrt9 <arch} **`viewer-url <owner/repository> <code-branch>`** prints
|
|
42
|
+
a direct link to that repository branch's hosted questionnaire. It URL
|
|
43
|
+
encodes both values and uses `IDD_VIEWER_URL` when set, otherwise the
|
|
44
|
+
package's configured public viewer address. It does not read a GitHub
|
|
45
|
+
credential or change files.
|
|
46
|
+
* {#s-dal2u7 <arch} **`wait <question-id> [interval-seconds]`** subscribes to
|
|
47
|
+
the hosted viewer's event stream and checks the matching remote IDD branch
|
|
48
|
+
through the current worktree's configured Git remote when the wait starts
|
|
49
|
+
and when an invalidation arrives. It defaults to a 30-second interval and
|
|
50
|
+
accepts intervals from 1 to 3600 seconds; it checks the remote branch at
|
|
51
|
+
that interval while push delivery is unavailable or the event stream is
|
|
52
|
+
disconnected. When the question has an accepted answer, it fast-forwards
|
|
53
|
+
the clean local data worktree, records that already-accepted answer in
|
|
54
|
+
`idd/pending.json` without rewriting `idd/answers.json`, prints the answer
|
|
55
|
+
as JSON and exits 0.
|
|
56
|
+
* {#s-854kw7 <arch} `wait` refuses to run from a dirty worktree, does not
|
|
57
|
+
commit or push, and uses the repository's configured Git credentials
|
|
58
|
+
without storing or displaying them. It exits 1 if the remote cannot be
|
|
59
|
+
fetched or fast-forwarded, and exits 130 when interrupted.
|
|
60
|
+
* {#s-dvurdo <arch} **Usage errors.** With no subcommand, an unknown one, or the
|
|
61
|
+
wrong number of arguments, it prints a usage line to stderr, writes
|
|
62
|
+
nothing and exits 2.
|
|
63
|
+
|
|
64
|
+
## branch-hash
|
|
65
|
+
|
|
66
|
+
* {#s-2bfbql <arch} **`branch-hash <repository-id> <full-ref>`** prints the lowercase
|
|
67
|
+
SHA-256 digest of the UTF-8 JSON serialization of `[String(repository-id),
|
|
68
|
+
full-ref]` and exits 0; `<full-ref>` is a full Git ref such as
|
|
69
|
+
`refs/heads/main`.
|
|
70
|
+
* {#s-0ys3dr <arch} **Refused input.** When the repository id is not a positive
|
|
71
|
+
decimal integer or the full ref does not begin with `refs/heads/` and name
|
|
72
|
+
a non-empty branch, the command prints the problem to stderr and exits 2.
|
|
73
|
+
|
|
74
|
+
## status
|
|
75
|
+
|
|
76
|
+
* {#s-zqk718 <arch} **`status <session-id> <blocked|working|clear>`** updates
|
|
77
|
+
`idd/status.json`: `blocked` and `working` store the selected boolean
|
|
78
|
+
and the current ISO 8601 `updatedAt` under the session id; `clear` removes
|
|
79
|
+
that entry and leaves an empty object when it was the last one.
|
|
80
|
+
* {#s-qvff9k <arch} **Session id and file validation.** A session id is a non-empty
|
|
81
|
+
string of at most 128 ASCII letters, digits, periods, underscores, colons or
|
|
82
|
+
hyphens. The command refuses malformed status data without changing the
|
|
83
|
+
file with exit 1; an invalid session id or state exits 2. `check` validates
|
|
84
|
+
every entry by `specs/hosted-questionnaire.md`. Clearing an absent entry
|
|
85
|
+
succeeds without writing the file.
|
|
86
|
+
|
|
87
|
+
## count
|
|
88
|
+
|
|
89
|
+
* {#s-vwase3 <m-xoqfn9@0df3826} **`count`** reports the counts of conflicting, unanswered,
|
|
90
|
+
answered and superseded questions in the current IDD data branch. With no
|
|
91
|
+
`--type` option, it prints one `<type>: <count>` line for each, in that order;
|
|
92
|
+
with `--type <type>`, it prints only that count as a decimal integer.
|
|
93
|
+
* {#s-4w8iuj <m-xoqfn9@0df3826,m-xhv3ci@566158e} **Count types.** `--type` accepts `c`, `u`, `a`, `s` and their
|
|
94
|
+
full names `conflicts`, `unanswered`, `answered`, and `superseded`. `s` is
|
|
95
|
+
the alias for superseded questions.
|
|
96
|
+
* {#s-83u8i8 <arch} **Conflicting questions.** Count each active question once when
|
|
97
|
+
at least one rejected answer has a different choice or note from its
|
|
98
|
+
accepted answer, or when it has a rejected answer and no accepted answer.
|
|
99
|
+
* {#s-1mid3a <arch} **Unanswered questions.** Count each active question once when
|
|
100
|
+
it has neither an accepted answer with a choice or note nor a conflicting
|
|
101
|
+
rejected answer.
|
|
102
|
+
* {#s-6uvlo6 <arch} **Answered questions.** Count each active question once when
|
|
103
|
+
it has an accepted answer with a choice or a non-empty note and no
|
|
104
|
+
conflicting rejected answer.
|
|
105
|
+
* {#s-t8xsan <m-xhv3ci@566158e} **Superseded questions.** Count the entries in the IDD branch's
|
|
106
|
+
question history file; each entry is one superseded question.
|
|
107
|
+
* {#s-jpjbwh <m-xoqfn9@0df3826} **Sync by default.** Unless `--nosync` is supplied, `count`
|
|
108
|
+
requires a clean Git worktree checked out on an `idd-<branch>` branch,
|
|
109
|
+
fetches `refs/heads/<current-branch>` from `origin`, and fast-forwards the
|
|
110
|
+
worktree before reading question data. It never commits or pushes.
|
|
111
|
+
* {#s-yufw52 <m-xoqfn9@0df3826} **No sync.** `--nosync` skips the fetch and fast-forward and
|
|
112
|
+
counts the current worktree contents without requiring it to be clean.
|
|
113
|
+
* {#s-14wrxl <arch} **Usage errors.** `--type` requires one supported type, may be
|
|
114
|
+
supplied once, and `--nosync` may be supplied once. Unknown options,
|
|
115
|
+
unsupported types or repeated flags print usage to stderr and exit 2.
|
|
116
|
+
* {#s-amd1ad <arch} **Sync errors.** With `idd/page.json` present, a non-IDD
|
|
117
|
+
branch, dirty worktree during sync,
|
|
118
|
+
fetch failure or non-fast-forward update prints an error to stderr and exits
|
|
119
|
+
1 without changing tracked questionnaire files. Invalid question data exits
|
|
120
|
+
1 with the validation problems on stderr and prints no counts.
|
|
121
|
+
|
|
122
|
+
## thoughts
|
|
123
|
+
|
|
124
|
+
* {#s-8hvj7z <arch} **`thoughts [--nosync]`** lists the project's thoughts from
|
|
125
|
+
`idd/thoughts.json` in append order, printing each thought's id, timestamp
|
|
126
|
+
and plain text. It prints `idd: no thoughts` when the file is absent or
|
|
127
|
+
empty. It does not change files.
|
|
128
|
+
* {#s-wapj39 <arch} By default, `thoughts` requires a clean worktree checked out on
|
|
129
|
+
an `idd-<branch>` branch, fetches the matching remote branch from `origin`
|
|
130
|
+
and fast-forwards before reading. `--nosync` skips synchronization and
|
|
131
|
+
reads the current worktree contents. It never commits or pushes.
|
|
132
|
+
* {#s-twjecx <arch} `thoughts` validates each record against
|
|
133
|
+
`specs/project-files.md`; invalid data prints problems to stderr, prints no
|
|
134
|
+
thoughts and exits 1. A non-IDD branch, dirty worktree during sync, fetch
|
|
135
|
+
failure or non-fast-forward update also exits 1 without changing tracked
|
|
136
|
+
questionnaire files.
|
|
137
|
+
* {#s-g5gxep <arch} `check` validates `idd/thoughts.json` when present, including
|
|
138
|
+
exact members, non-empty text, valid branch hash, ISO 8601 timestamp,
|
|
139
|
+
unique ids, and each id's digest.
|
|
140
|
+
|
|
141
|
+
## init
|
|
142
|
+
|
|
143
|
+
* {#s-51ncq9 <arch} **`init <title> <branch-hash>`** writes `idd/page.json`
|
|
144
|
+
holding `title` set to `<title>`, `url` set to `null`, and `branchHash`
|
|
145
|
+
set to `<branch-hash>`, and `idd/questions.json` holding an empty array,
|
|
146
|
+
then exits 0.
|
|
147
|
+
* {#s-cd2jke <arch} **It never overwrites.** When either file exists, `init` writes
|
|
148
|
+
nothing, prints a line naming the existing file to stderr and exits 2.
|
|
149
|
+
|
|
150
|
+
## check
|
|
151
|
+
|
|
152
|
+
* {#s-hcsr5l <m-f2iwx9@98efad1} **`check`** reads both files and prints to stdout one line per
|
|
153
|
+
way they differ from `specs/project-files.md`, each `<file>: <problem>`
|
|
154
|
+
naming the question id where the problem is in a question, and exits 1
|
|
155
|
+
when it printed any; {#s-9exkpo <arch} with no problem it prints `idd: ok` and
|
|
156
|
+
exits 0.
|
|
157
|
+
* {#s-2b5w6i <arch} **What it catches.** It reports a file that is not JSON, a
|
|
158
|
+
member missing, of the wrong form or not named by
|
|
159
|
+
`specs/project-files.md`, a branch hash that is not 64 lowercase
|
|
160
|
+
hexadecimal characters, a question id that differs from its hash of the
|
|
161
|
+
question's own `branchHash` and content, an id two questions share,
|
|
162
|
+
an options array of fewer than two or more than four options, and a key
|
|
163
|
+
two options of one question share, or a `createdAt` that is not an ISO
|
|
164
|
+
8601 time; it also checks question snapshots and replacement links in
|
|
165
|
+
`idd/history.json`, and accepts answers and pending answers for active or
|
|
166
|
+
historical question ids; when `idd/conflicts.json` exists, it validates
|
|
167
|
+
every rejected answer against its question and options.
|
|
168
|
+
* {#s-pmnqlm <arch} **The answer file.** When `idd/answers.json` exists, `check`
|
|
169
|
+
also reports each way it differs from `specs/project-files.md`, The
|
|
170
|
+
answer file: a file that is not JSON or not an object, a member whose
|
|
171
|
+
name is no question's id, a value missing a member, holding another or
|
|
172
|
+
holding one of the wrong form, and a `choice` that is neither `""` nor a
|
|
173
|
+
key of its question's options. {#s-a1zj4t <arch} An absent answer file is not a
|
|
174
|
+
problem.
|
|
175
|
+
* {#s-ikua3t <arch} **The pending answer file.** When `idd/pending.json` exists, `check`
|
|
176
|
+
reports each way it differs from `specs/project-files.md`, The pending
|
|
177
|
+
answer file, including an answer that differs from the answer file.
|
|
178
|
+
* {#s-sse100 <arch} **No page file.** When `idd/page.json` does not exist, every
|
|
179
|
+
subcommand but `init`, `guide`, `viewer-url` and `branch-hash` exits 2 and
|
|
180
|
+
names the missing page file on stderr. `import`, when the question file does
|
|
181
|
+
not already hold questions, reports the missing page through its own import
|
|
182
|
+
error because it needs the page's branch hash to create question ids.
|
|
183
|
+
|
|
184
|
+
## build
|
|
185
|
+
|
|
186
|
+
* {#s-qzz6xu <m-f2iwx9@98efad1} **`build`** runs the check; when the check reports
|
|
187
|
+
any problem it prints the problems, writes nothing and exits 1;
|
|
188
|
+
otherwise it writes `tmp/idd/page.html`, creating `tmp/idd/` when absent,
|
|
189
|
+
prints that path and exits 0.
|
|
190
|
+
* {#s-gcsqgg <m-f2iwx9@98efad1} **The built page.** The built page is the questions page
|
|
191
|
+
(`specs/questions-page.md`) drawing exactly the question file's questions
|
|
192
|
+
in its order, titled with the page file's title.
|
|
193
|
+
* {#s-t2pr4c <arch} **Any text survives.** A title, body, option text or tag holding
|
|
194
|
+
any characters, `</script>`, quotes, backslashes and non-ASCII text
|
|
195
|
+
among them, is drawn by the built page exactly as the question file holds
|
|
196
|
+
it.
|
|
197
|
+
|
|
198
|
+
## add
|
|
199
|
+
|
|
200
|
+
* {#s-mvtsgu <m-f2iwx9@98efad1,m-edvy4l@54198de} **`add`** reads from stdin one JSON object
|
|
201
|
+
holding exactly the members `title`, `body` and `options`, each in the
|
|
202
|
+
form `specs/project-files.md` gives a question's (The question file),
|
|
203
|
+
appends the reserved X option from `specs/project-files.md` when absent,
|
|
204
|
+
computes its id as the lowercase SHA-256 digest of the UTF-8 JSON
|
|
205
|
+
serialization of `[branchHash, title, body, options]`, with each option,
|
|
206
|
+
including reserved X, represented as `[key, text, tag-or-empty-string]`, where `branchHash` is
|
|
207
|
+
the current page file's branch hash; stores that hash in the question,
|
|
208
|
+
adds the current ISO 8601 time as `createdAt`, appends it to
|
|
209
|
+
`idd/questions.json`, prints the id to stdout and exits 0. A legacy
|
|
210
|
+
question whose id predates X being included retains its id; the checker
|
|
211
|
+
accepts that prior formula, and duplicate detection compares a legacy
|
|
212
|
+
question after adding X for display so the same question cannot be added
|
|
213
|
+
again under a different id.
|
|
214
|
+
* {#s-hbdizs <arch} **Only the end changes.** `add` leaves every question the file
|
|
215
|
+
held, and their order, as it was.
|
|
216
|
+
* {#s-zopa6h <arch} **Refused input.** When stdin does not hold such an object,
|
|
217
|
+
`add` writes nothing, prints one line per problem to stderr, each naming
|
|
218
|
+
the member at fault, and exits 2. When the computed id is already in the
|
|
219
|
+
question file, including after normalizing a legacy question by adding the
|
|
220
|
+
reserved X option, it writes nothing, shows the existing question, and exits 2.
|
|
221
|
+
* {#s-yx8tuh <arch} **Files that fail the check.** After stdin passes `add`'s
|
|
222
|
+
input validation, when the check reports any problem in the project's files
|
|
223
|
+
as they are, `add` prints the problems, writes nothing and exits 1.
|
|
224
|
+
* {#s-xrkyna <arch} **No id-only subcommand.** No subcommand prints a question id
|
|
225
|
+
without adding the question: `next-id` is an unknown subcommand.
|
|
226
|
+
|
|
227
|
+
## archive
|
|
228
|
+
|
|
229
|
+
* {#s-9oyy6g <arch} **`archive <question-id> <replacement-id>`** moves the active
|
|
230
|
+
question named by `question-id` to `idd/history.json` with its complete
|
|
231
|
+
question snapshot, the replacement id and the current ISO 8601 time; it
|
|
232
|
+
removes the question from `idd/questions.json` and leaves its answer and
|
|
233
|
+
pending answer unchanged.
|
|
234
|
+
* {#s-f9mmtf <arch} **Refused input.** It writes nothing and exits 2 when either id
|
|
235
|
+
is invalid, the question is not active, the replacement is not active, both
|
|
236
|
+
ids are equal, or the question already appears in history. It writes nothing
|
|
237
|
+
and exits 1 when the current project files fail `check`.
|
|
238
|
+
|
|
239
|
+
## answer
|
|
240
|
+
|
|
241
|
+
* {#s-2s3q95 <arch} **`answer <id>`** reads from stdin one JSON object holding
|
|
242
|
+
`sChoice`, `sNote` and `dtm`, in the form of the page's answer document
|
|
243
|
+
(`specs/questions-page.md`, API), and records it in `idd/answers.json` as
|
|
244
|
+
the member `<id>` whose `choice`, `note` and `time` are their values
|
|
245
|
+
(`specs/project-files.md`, The answer file), then exits 0. {#s-akhsa6 <arch} It
|
|
246
|
+
creates the file when it is absent and leaves every other member as it
|
|
247
|
+
was. {#s-19wno0 <arch} It also records the same answer under `<id>` in
|
|
248
|
+
`idd/pending.json`, creating that file when absent, and leaves every other
|
|
249
|
+
pending member as it was. {#s-pos9d5 <arch} Any other
|
|
250
|
+
member of the object read is ignored.
|
|
251
|
+
* {#s-y1y75p <arch} **An accepted answer cannot be replaced.** With the page
|
|
252
|
+
file present, when the answer file
|
|
253
|
+
already holds `<id>`, `answer` writes nothing, prints the accepted answer
|
|
254
|
+
to stderr and exits 2.
|
|
255
|
+
* {#s-o5jdcy <arch} **Refused input.** When `<id>` is the id of no question of the
|
|
256
|
+
question file, stdin does not hold such an object, `sChoice` is neither
|
|
257
|
+
`""` nor a key of that question's options, `sNote` is not a string, or
|
|
258
|
+
`dtm` is not an ISO 8601 time, `answer` writes nothing, prints a line
|
|
259
|
+
naming the problem to stderr and exits 2.
|
|
260
|
+
|
|
261
|
+
## clear-pending
|
|
262
|
+
|
|
263
|
+
* {#s-09iime <arch} **`clear-pending <id> <time>`** removes `<id>` from
|
|
264
|
+
`idd/pending.json` only when its recorded `time` exactly equals `<time>`;
|
|
265
|
+
when the id is absent or its time differs, it writes nothing and exits 0;
|
|
266
|
+
when the last member is removed, it leaves the file as an empty object.
|
|
267
|
+
* {#s-p66ewo <arch} **Refused input.** When `<id>` is the id of no question or `<time>` is
|
|
268
|
+
not an ISO 8601 time, `clear-pending` writes nothing, prints the problem
|
|
269
|
+
to stderr and exits 2.
|
|
270
|
+
|
|
271
|
+
## import
|
|
272
|
+
|
|
273
|
+
* {#s-hnxl58 <arch} **`import <page>`** reads `<page>`, an HTML page made from the
|
|
274
|
+
older template, which holds its questions as a JavaScript array literal
|
|
275
|
+
assigned to `const vaQuestion`, and writes them, in the same order, to
|
|
276
|
+
`idd/questions.json` in the form `specs/project-files.md` gives.
|
|
277
|
+
* {#s-vhstia <arch} **Keys.** Each question's `sTitle` and `sBody` become
|
|
278
|
+
`title` and `body`, its `vaOpt` becomes `options`, and each option's
|
|
279
|
+
`sKey`, `sText` and `sTag` become `key`, `text` and `tag`; its id is
|
|
280
|
+
computed from the page file's `branchHash` and converted question fields,
|
|
281
|
+
and `createdAt` is the time the import runs.
|
|
282
|
+
* {#s-tasgnp <arch} **Bodies become text.** An imported body is
|
|
283
|
+
converted from HTML to text: each `<code>…</code>` becomes the same text
|
|
284
|
+
between backticks, each `<br>` and each paragraph break becomes a blank
|
|
285
|
+
line, every other tag is removed and its text kept, and every character
|
|
286
|
+
reference, as `<`, becomes its character.
|
|
287
|
+
* {#s-yl6bcx <arch} **Titles, option texts and tags stay text.** An imported title,
|
|
288
|
+
option text or tag is not converted from HTML: no tag in it is removed and
|
|
289
|
+
no backtick or blank line is added, so a title `<analyze> reading a
|
|
290
|
+
pop-up` imports as `<analyze> reading a pop-up`.
|
|
291
|
+
* {#s-irxy7e <arch} **Their character references are decoded.** In an imported
|
|
292
|
+
title, option text or tag, each `<`, `>`, `"` and `&`, and
|
|
293
|
+
each numeric character reference, decimal (`<`) or hexadecimal
|
|
294
|
+
(`<`), becomes its character, decoded once, so `&lt;` becomes
|
|
295
|
+
`<`; every other character, other named references among them, is kept
|
|
296
|
+
as written.
|
|
297
|
+
* {#s-zmh8z6 <arch} **It never overwrites questions.** When `idd/questions.json`
|
|
298
|
+
exists and holds any question, `import` writes nothing, prints a line
|
|
299
|
+
naming the file to stderr and exits 2.
|
|
300
|
+
* {#s-51kf4i <arch} **An unreadable page.** When `<page>` cannot be read or holds
|
|
301
|
+
no `vaQuestion` array literal it can read, `import` writes nothing, prints
|
|
302
|
+
a line naming the page and the problem to stderr and exits 2.
|
|
303
|
+
|
|
304
|
+
## Concurrent runs
|
|
305
|
+
|
|
306
|
+
* {#s-lzdklu <m-edvy4l@54198de} **Concurrent adds preserve distinct questions.** Any number of
|
|
307
|
+
`add` runs started at once on one project preserve every distinct
|
|
308
|
+
question they add, each under its computed id; identical questions share
|
|
309
|
+
an id and only one is stored.
|
|
310
|
+
* {#s-vhcbdr <m-edvy4l@54198de} **No answer is lost.** Any number of `answer` runs started at
|
|
311
|
+
once on one project for different ids leave every one of their answers in
|
|
312
|
+
the answer file.
|
|
313
|
+
* {#s-z8braj <arch} **One step from read to write.** Every subcommand that writes a
|
|
314
|
+
project file makes its decision on the files as they are at the moment it
|
|
315
|
+
writes: no other run writes a project file between the read it decides on
|
|
316
|
+
and its write.
|
|
317
|
+
* {#s-cals8z <arch} **No half-written file.** A run that reads a project file while
|
|
318
|
+
another run writes it reads the whole file as it was either before or
|
|
319
|
+
after that write.
|
|
320
|
+
* {#s-4y820y <arch} **Consistent checks and builds.** `check` and `build` take the project
|
|
321
|
+
lock while reading the project files, so they do not see the interval
|
|
322
|
+
between `answer` writing the answer file and its pending answer file.
|
|
323
|
+
* {#s-mm6l60 <arch} **Waiting for another run.** A run that must wait for another
|
|
324
|
+
run's write waits for it; when it has waited 10 seconds, it writes
|
|
325
|
+
nothing, prints a line to stderr saying the project's files are held by
|
|
326
|
+
another run, and exits 2. {#s-qt9vvj <arch} A run that ended, however it ended,
|
|
327
|
+
never makes a later run wait.
|
|
328
|
+
* {#s-zb5ycj <arch} **Nothing left behind.** Once a run has exited, the only files it
|
|
329
|
+
has created or changed are the project files it wrote and files under
|
|
330
|
+
`tmp/idd/`.
|
|
331
|
+
* {#s-rueqkl <arch} **One written form.** Every project file a subcommand writes is
|
|
332
|
+
JSON indented two spaces per level, a member written `"name": value`, and
|
|
333
|
+
ends with a newline, so that the full question digest appears as its `id`
|
|
334
|
+
in `idd/questions.json`.
|
|
335
|
+
|
|
336
|
+
## Dependencies
|
|
337
|
+
|
|
338
|
+
* {#p-tc5ji4 >specs/project-files.md} The files it creates, checks, reads and
|
|
339
|
+
records answers in.
|
|
340
|
+
* {#p-88dl9g >specs/questions-page.md} The page it builds.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Project files
|
|
2
|
+
|
|
3
|
+
Statement ids: complete
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
The files on an adopting project's matching IDD branch that hold its shared
|
|
8
|
+
questions, answers and pending answers, so sessions of any agent tool work
|
|
9
|
+
from the same question list and recorded results, while each tool presents
|
|
10
|
+
its own questionnaire.
|
|
11
|
+
|
|
12
|
+
## His words
|
|
13
|
+
|
|
14
|
+
> {#m-ec324x} RULING (CEO), 2026-11-10: “There should always be an
|
|
15
|
+
> answer (the last one) which is: X. None of the above are right. Let me
|
|
16
|
+
> explain: Also, by default, none of the answers should be selected.”
|
|
17
|
+
|
|
18
|
+
> {#m-q2caxw} RULING (CEO), 2026-11-12, IDD question
|
|
19
|
+
> `08a76cb8364300fab2aaf8345ba237d56cb66cafded6dc4143a7978193dc0fd7`:
|
|
20
|
+
> “Should the question id hash include the reserved X option? A) Exclude it,
|
|
21
|
+
> because it is added to older questions. B) Include it, because it can be
|
|
22
|
+
> chosen.” His answer: “B”. His note: “Since it can be chosen, it should be
|
|
23
|
+
> in the hash.”
|
|
24
|
+
|
|
25
|
+
> {#m-tu5fn2} From his instructions file `~/.claude/CLAUDE.md`, section
|
|
26
|
+
> "Questions for me go on a web questions page", as read 2026-11-04: "One
|
|
27
|
+
> page per session or project. Start from
|
|
28
|
+
> `~/.claude/templates/questions-page.html`. Copy it to the scratchpad,
|
|
29
|
+
> replace `__TITLE__` with "<Project> Open Questions", and replace the
|
|
30
|
+
> placeholder entry in `vaQuestion` with real questions."
|
|
31
|
+
|
|
32
|
+
> {#m-90lcy5} From the same section: "Each question is complete on its own.
|
|
33
|
+
> Give a short title, then a body that says what I see, where and when, with
|
|
34
|
+
> a concrete example, then 2–4 options as `{ sKey, sText }`."
|
|
35
|
+
|
|
36
|
+
> {#m-polrzx} RULING (CEO) 2026-11-02 (Concord), on `concord.json`: "That is
|
|
37
|
+
> weird. Concord.json should not use semantic hungarian tags. SH is really
|
|
38
|
+
> limited to code."
|
|
39
|
+
|
|
40
|
+
> {#m-edvy4l} RULING (CEO) 2026-11-04, q197. The question: "IDD: what do agent
|
|
41
|
+
> tools other than Claude Code do? The new IDD package (interrogation-driven
|
|
42
|
+
> development, in ~/@idd) puts every decision on a questions page like this
|
|
43
|
+
> one. The page is a claude.ai artifact, so a Codex session cannot publish it
|
|
44
|
+
> or read your answers. Example: a Codex session in AIOS needs to know
|
|
45
|
+
> whether a new button saves on click or on Enter.
|
|
46
|
+
> A) Codex writes the question into the project's question file and also
|
|
47
|
+
> asks it in full in the terminal; it appears on the page the next time a
|
|
48
|
+
> Claude Code session publishes it.
|
|
49
|
+
> B) Codex asks in the terminal only, by the same writing rules, and records
|
|
50
|
+
> your answer in the specs.
|
|
51
|
+
> C) IDD gets a local page any tool can serve, with answers saved to a file
|
|
52
|
+
> in the project; the claude.ai page is retired.
|
|
53
|
+
> D) IDD is for Claude Code sessions only, for now." His answer, choosing no
|
|
54
|
+
> option, with the note: "Each agent gets its own tool for interactive
|
|
55
|
+
> questionnaires. The results should be captured in a local file so that
|
|
56
|
+
> multiple agents can expand off of the same question list. For now, define
|
|
57
|
+
> the claude approach, and leave open how other tools will solve it for
|
|
58
|
+
> themselves (e.g. Codex, Gemini, etc.)"
|
|
59
|
+
|
|
60
|
+
## Terms
|
|
61
|
+
|
|
62
|
+
* {#d-wczec5 <m-tu5fn2@79447f9} **Page file** — `idd/page.json` at the root of an
|
|
63
|
+
adopting project's data worktree: the page's title and branch hash.
|
|
64
|
+
* {#d-q6pb2e <m-tu5fn2@79447f9} **Question file** — `idd/questions.json` at the root
|
|
65
|
+
of that data worktree: every question presented with an agent tool's
|
|
66
|
+
questionnaire.
|
|
67
|
+
* {#d-b4np1k <m-edvy4l@54198de} **Answer file** — `idd/answers.json` at the root of
|
|
68
|
+
that data worktree: the human's recorded answers.
|
|
69
|
+
* {#d-z044ah <arch} **Pending answer file** — `idd/pending.json` at the root of
|
|
70
|
+
that data worktree: answers a session has not yet incorporated.
|
|
71
|
+
## Shared by every agent tool
|
|
72
|
+
|
|
73
|
+
* {#g-f1rxwm <m-edvy4l@54198de for=main,architect via=prompt} **One question list.** A
|
|
74
|
+
session of any agent tool takes its question ids from, and adds its
|
|
75
|
+
questions to, the project's one question file, and records and reads the
|
|
76
|
+
human's answers in the project's answer file; no agent tool keeps a
|
|
77
|
+
question list or an answer record of its own.
|
|
78
|
+
* {#g-7pv35v <m-edvy4l@54198de,m-zbztp6@c44613a for=main,architect via=prompt} **Each tool its own
|
|
79
|
+
questionnaire.** How an agent tool puts the questions before the human and
|
|
80
|
+
collects his answers is that tool's own, and is specified in that tool's
|
|
81
|
+
guidance spec under `specs/guidance/`. IDD specifies Claude Code's
|
|
82
|
+
(`specs/guidance/claude-code.md`) and Codex's
|
|
83
|
+
(`specs/guidance/codex.md`).
|
|
84
|
+
|
|
85
|
+
## The page file
|
|
86
|
+
|
|
87
|
+
* {#s-f6ji64 <m-tu5fn2@79447f9,m-polrzx@d3b1d54} **Its members.** The page file holds one JSON
|
|
88
|
+
object with exactly three members: `title`, a non-empty string, as in
|
|
89
|
+
`"title": "AIOS Open Questions"`; `url`, the address the page is
|
|
90
|
+
published at, a non-empty string, or `null` while the page has never been
|
|
91
|
+
published; and `branchHash`, a lowercase, 64-character SHA-256 digest
|
|
92
|
+
identifying the source code branch whose questionnaire it holds.
|
|
93
|
+
|
|
94
|
+
## The question file
|
|
95
|
+
|
|
96
|
+
* {#s-rhzch5 <m-tu5fn2@79447f9,m-polrzx@d3b1d54} **An array.** The question file holds one JSON
|
|
97
|
+
array of questions, in the order they were presented to the human.
|
|
98
|
+
* {#s-0yj34e <m-90lcy5@5b4a86e,m-polrzx@d3b1d54} **A question.** Each question is an object with
|
|
99
|
+
exactly the members `id`, `branchHash`, `title`, `body`, `options` and
|
|
100
|
+
`createdAt`. {#s-bjouc5 <arch,m-ec324x@c9504b3,m-q2caxw@e07030c} `id` is the lowercase,
|
|
101
|
+
64-character SHA-256 digest of the UTF-8 JSON serialization of
|
|
102
|
+
`[branchHash, title, body, options]`, with every option represented as
|
|
103
|
+
`[key, text, tag-or-empty-string]`, including the reserved final X option;
|
|
104
|
+
`branchHash` is the hash of the code branch where the question was first
|
|
105
|
+
created. A legacy question whose id predates including X retains its stored
|
|
106
|
+
id and is accepted under the prior formula; adding X for display does not
|
|
107
|
+
rewrite its identity. New questions with the same canonical content and
|
|
108
|
+
branch hash share one id, preventing duplicate questions. `createdAt` is
|
|
109
|
+
the ISO 8601 time the question was added.
|
|
110
|
+
{#s-s0obkh <m-90lcy5@5b4a86e} `title` and
|
|
111
|
+
`body` are non-empty strings. {#s-twjma0 <m-90lcy5@5b4a86e} `options` is an array of
|
|
112
|
+
two to four substantive options, optionally followed by the reserved final
|
|
113
|
+
option `{"key":"X","text":"None of the above are right. Let me explain:"}`.
|
|
114
|
+
{#s-wznig4 <m-ec324x@c9504b3,m-q2caxw@e07030c} A newly added question stores this reserved option
|
|
115
|
+
last; a legacy question without it remains valid and the viewer supplies it
|
|
116
|
+
when displaying the question. No other option may use key `X`.
|
|
117
|
+
* {#s-0yp4n2 <m-90lcy5@5b4a86e,m-polrzx@d3b1d54} **An option.** Each option is an object with the
|
|
118
|
+
members `key`, a non-empty string no other option of its question holds,
|
|
119
|
+
and `text`, a non-empty string, and optionally `tag`, a non-empty string
|
|
120
|
+
drawn under the option's text; it has no other member.
|
|
121
|
+
* {#s-fvhi3x <arch} **Bodies are text.** A body is plain text: backticks mark code
|
|
122
|
+
spans and a blank line separates paragraphs; HTML in a body is text, never
|
|
123
|
+
markup (`specs/questions-page.md`, What the page draws).
|
|
124
|
+
|
|
125
|
+
## The answer file
|
|
126
|
+
|
|
127
|
+
* {#s-hri503 <arch} **Its members.** The answer file holds one JSON object. Each
|
|
128
|
+
member's name is the id of a question in the active question file or
|
|
129
|
+
question history, and its value
|
|
130
|
+
is an object with exactly the members `choice`, the key of the option he
|
|
131
|
+
chose, a string, or `""` when he chose none; `note`, his note, a string,
|
|
132
|
+
`""` when he wrote none; and `time`, the time he saved the answer, an ISO
|
|
133
|
+
8601 string.
|
|
134
|
+
* {#s-zrui8r <arch} **One accepted answer per question.** The answer file holds at
|
|
135
|
+
most one accepted answer per question. An accepted answer is never
|
|
136
|
+
replaced; a later submission is rejected and its submitter is shown the
|
|
137
|
+
accepted answer (`specs/idd-page.md`, answer; `specs/hosted-questionnaire.md`).
|
|
138
|
+
* {#s-r40lfj <arch} **Absent means none recorded.** A project with no answer file
|
|
139
|
+
has no recorded answer; the first answer recorded creates it.
|
|
140
|
+
|
|
141
|
+
## The question history file
|
|
142
|
+
|
|
143
|
+
* {#s-g7mf6n <arch} `idd/history.json` holds a JSON array. Each entry has exactly
|
|
144
|
+
`questionId`, a full `question` snapshot of the removed question,
|
|
145
|
+
`replacedBy`, the id of its replacement, and `time`, the ISO 8601 time
|
|
146
|
+
it was removed from the active list. The snapshot's `id` equals
|
|
147
|
+
`questionId`.
|
|
148
|
+
* {#s-0fiha8 <arch} An answer to a question in history remains in
|
|
149
|
+
`idd/answers.json` under its original `questionId`; it is not deleted or
|
|
150
|
+
rewritten during reconciliation.
|
|
151
|
+
|
|
152
|
+
## The pending answer file
|
|
153
|
+
|
|
154
|
+
* {#s-0ra8q5 <arch} **Its members.** The pending answer file holds one JSON object. Each
|
|
155
|
+
member's name is the id of a question in the active question file or
|
|
156
|
+
question history, and its value
|
|
157
|
+
has the same `choice`, `note` and `time` members and forms as an answer
|
|
158
|
+
file entry.
|
|
159
|
+
* {#s-ejxfi4 <arch} **A pending answer is recorded.** Each member of the pending answer file
|
|
160
|
+
has an entry with the same id, choice, note and time in the answer file.
|
|
161
|
+
* {#s-u52jq6 <arch} **Absent means none pending.** A project with no pending answer file has
|
|
162
|
+
no locally pending answer; recording the first answer creates it.
|
|
163
|
+
|
|
164
|
+
## Thoughts and observations
|
|
165
|
+
|
|
166
|
+
* {#s-61lmfb <arch} `idd/thoughts.json` holds one JSON array of thoughts in append
|
|
167
|
+
order. A thought has exactly `id`, `branchHash`, `text`, and `createdAt`.
|
|
168
|
+
`id` is a lowercase, 64-character SHA-256 digest of the UTF-8 JSON
|
|
169
|
+
serialization of `[branchHash, createdAt, text]`. `branchHash` identifies
|
|
170
|
+
the code branch, `text` is the non-empty plain-text statement, and
|
|
171
|
+
`createdAt` is its unique ISO 8601 submission time. Thoughts are separate from questions,
|
|
172
|
+
answers, conflicts and their counts.
|
|
173
|
+
* {#s-bv05e2 <arch} A thought is immutable after submission. Repeating identical text
|
|
174
|
+
creates a distinct thought with a distinct id; appending never replaces or
|
|
175
|
+
merges an existing thought.
|
|
176
|
+
* {#s-axz9ts <arch} If `idd/thoughts.json` is absent, the project has no submitted
|
|
177
|
+
thoughts. The first submission creates it.
|
|
178
|
+
|
|
179
|
+
## Where the built page goes
|
|
180
|
+
|
|
181
|
+
* {#s-9e2f7d <arch} **Built under `tmp/`.** `idd build` writes the page to
|
|
182
|
+
`tmp/idd/page.html` under the data worktree root; it is the file the session
|
|
183
|
+
publishes, and is never committed.
|
|
184
|
+
|
|
185
|
+
## Dependencies
|
|
186
|
+
|
|
187
|
+
* {#p-8khzfb >specs/idd-page.md} The command that creates, checks and builds from
|
|
188
|
+
these files.
|