@debonet/idd 0.1.0 → 0.1.1
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 +18 -0
- package/package.json +15 -5
- package/scripts/check.js +56 -1
- package/scripts/commands.js +65 -4
- package/specs/build.md +26 -1
- package/specs/guidance/codex.md +8 -0
- package/specs/guidance/session.md +15 -0
- package/specs/hosted-questionnaire.md +58 -3
- package/specs/idd-page.md +28 -1
- package/specs/project-files.md +13 -3
- package/specs/themes.md +23 -3
package/INSTRUCTIONS.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# IDD instructions for coding agents
|
|
2
2
|
|
|
3
|
+
<!-- @implements g-mfqz8n@63b06e1 -->
|
|
4
|
+
|
|
3
5
|
This project uses IDD when its `AGENTS.md` says so. The npm package supplies
|
|
4
6
|
the agent workflow and commands. The hosted questionnaire runs separately at
|
|
5
7
|
`https://interrogation.dev`; installing the npm package does not install or
|
|
@@ -33,6 +35,13 @@ and grant it access to that repository.
|
|
|
33
35
|
thoughts in creation order. Work on a note when it applies and has not
|
|
34
36
|
already been addressed. Ask through the usual IDD question flow when a
|
|
35
37
|
note needs a decision. Leave submitted notes in the shared log.
|
|
38
|
+
6. Before incorporating an answer or acting on a thought or conflict, claim
|
|
39
|
+
that item with `npm exec -- idd claim <answer|thought|conflict>
|
|
40
|
+
<item-id> <session-id>`. Commit and push `idd/claims.json` before starting
|
|
41
|
+
the work. If the push is rejected, sync and inspect the branch; proceed
|
|
42
|
+
only if this session owns the claim. Release it with
|
|
43
|
+
`npm exec -- idd release <kind> <item-id> <session-id>` after the work is
|
|
44
|
+
incorporated, then commit and push the release.
|
|
36
45
|
|
|
37
46
|
For a project's first IDD branch, get its numeric GitHub repository id and
|
|
38
47
|
run `npm exec -- idd branch-hash <repository-id>
|
|
@@ -86,6 +95,15 @@ with `sChoice`, `sNote` and `dtm` (the current ISO 8601 time). Commit and
|
|
|
86
95
|
push the answer files to the IDD branch before continuing. Commit and push
|
|
87
96
|
changes to `pending.json` when you record an answer and after you clear it,
|
|
88
97
|
so another session can see the remaining work.
|
|
98
|
+
|
|
99
|
+
In Codex, connect the IDD MCP server once with
|
|
100
|
+
`codex mcp add idd --url https://interrogation.dev/mcp`, then restart Codex.
|
|
101
|
+
While waiting for a hosted answer, keep an `idd_wait_for_change` MCP call active
|
|
102
|
+
and run `npm exec -- idd wait <question-id>` as a second listener. The MCP tool
|
|
103
|
+
signals any IDD branch change; `idd wait` synchronizes and records that
|
|
104
|
+
question's answer. After either signal, inspect the latest branch state. Both
|
|
105
|
+
may report the same update, so check pending data before incorporating an answer.
|
|
106
|
+
|
|
89
107
|
Never replace an answer already accepted on the IDD branch; show the
|
|
90
108
|
accepted answer if another submission conflicts.
|
|
91
109
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@debonet/idd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Interrogation-driven development: the questions page and its command",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/debonet/idd"
|
|
8
|
+
},
|
|
5
9
|
"publishConfig": {
|
|
6
10
|
"access": "public"
|
|
7
11
|
},
|
|
@@ -21,15 +25,18 @@
|
|
|
21
25
|
"specs/",
|
|
22
26
|
"templates/"
|
|
23
27
|
],
|
|
24
|
-
"workspaces": [
|
|
28
|
+
"workspaces": [
|
|
29
|
+
"web"
|
|
30
|
+
],
|
|
25
31
|
"bin": {
|
|
26
32
|
"idd": "bin/idd",
|
|
27
33
|
"idd-merge-hook": "bin/idd-merge-hook"
|
|
28
34
|
},
|
|
29
35
|
"scripts": {
|
|
30
36
|
"start": "node web/server.js",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
37
|
+
"build:web": "node scripts/build-web.js",
|
|
38
|
+
"deploy": "npm run build:web && node scripts/deploy.js",
|
|
39
|
+
"test": "npm run build:web && jest",
|
|
33
40
|
"spec-map": "$HOME/@concord/bin/spec-map",
|
|
34
41
|
"spec-coverage": "$HOME/@concord/bin/spec-coverage",
|
|
35
42
|
"hook-install": "$HOME/@concord/bin/concord-hook install"
|
|
@@ -38,12 +45,15 @@
|
|
|
38
45
|
"node": ">=20"
|
|
39
46
|
},
|
|
40
47
|
"devDependencies": {
|
|
48
|
+
"esbuild": "^0.28.2",
|
|
41
49
|
"jest": "^29.7.0",
|
|
42
50
|
"jsdom": "^24.1.0"
|
|
43
51
|
},
|
|
44
52
|
"jest": {
|
|
45
53
|
"rootDir": ".",
|
|
46
|
-
"roots": [
|
|
54
|
+
"roots": [
|
|
55
|
+
"<rootDir>/test"
|
|
56
|
+
],
|
|
47
57
|
"testEnvironment": "node"
|
|
48
58
|
}
|
|
49
59
|
}
|
package/scripts/check.js
CHANGED
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
// @implements s-r40lfj@d88f3b2
|
|
27
27
|
// @implements s-g5gxep@784430f
|
|
28
28
|
// @implements s-61lmfb@6451bc6
|
|
29
|
+
// @implements s-0u0b06@9b94e8d
|
|
30
|
+
// @implements s-8ffele@5deea72
|
|
31
|
+
// @implements s-upxu0m@00fd9f6
|
|
29
32
|
const crypto = require( "crypto" )
|
|
30
33
|
const sFilePage = "idd/page.json"
|
|
31
34
|
const sFileQuestion = "idd/questions.json"
|
|
@@ -35,6 +38,7 @@ const sFileStatus = "idd/status.json"
|
|
|
35
38
|
const sFileHistory = "idd/history.json"
|
|
36
39
|
const sFileConflict = "idd/conflicts.json"
|
|
37
40
|
const sFileThoughts = "idd/thoughts.json"
|
|
41
|
+
const sFileClaims = "idd/claims.json"
|
|
38
42
|
|
|
39
43
|
function fbString( x ){
|
|
40
44
|
return typeof x == "string" && x.length > 0
|
|
@@ -388,6 +392,28 @@ function fvsStatusProblem( aStatus ){
|
|
|
388
392
|
})
|
|
389
393
|
}
|
|
390
394
|
|
|
395
|
+
function fvsClaimsProblem( aClaims, aWorkIds ){
|
|
396
|
+
if( !fbObject( aClaims )) return [ "not a JSON object" ]
|
|
397
|
+
return Object.entries( aClaims ).flatMap(([ sKey, aClaim ]) => {
|
|
398
|
+
const vsProblem = []
|
|
399
|
+
const aMatch = sKey.match( /^(answer|thought|conflict):([0-9a-f]{64})$/ )
|
|
400
|
+
if( !aMatch ) vsProblem.push( sKey + ": invalid claim key" )
|
|
401
|
+
if( !fbObject( aClaim )) return [ ...vsProblem, sKey + ": claim is not an object" ]
|
|
402
|
+
vsProblem.push( ...fvsMemberProblem( aClaim,
|
|
403
|
+
[ "sessionId", "claimedAt" ], [ "sessionId", "claimedAt" ], sKey + ": " ))
|
|
404
|
+
if( "sessionId" in aClaim && !/^[A-Za-z0-9._:-]{1,128}$/.test( aClaim.sessionId )){
|
|
405
|
+
vsProblem.push( sKey + ": invalid session id" )
|
|
406
|
+
}
|
|
407
|
+
if( "claimedAt" in aClaim && !fbIsoTime( aClaim.claimedAt )){
|
|
408
|
+
vsProblem.push( sKey + ": claimedAt is not an ISO 8601 time" )
|
|
409
|
+
}
|
|
410
|
+
if( aMatch && aWorkIds[ aMatch[ 1 ]] && !aWorkIds[ aMatch[ 1 ]].has( aMatch[ 2 ])){
|
|
411
|
+
vsProblem.push( sKey + ": item id is not present in its source file" )
|
|
412
|
+
}
|
|
413
|
+
return vsProblem
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
391
417
|
function faActiveQuestionIds( sTextQuestion ){
|
|
392
418
|
try {
|
|
393
419
|
const vaQuestion = JSON.parse( sTextQuestion )
|
|
@@ -493,7 +519,7 @@ function fvsParsed( sFile, sText, fvsProblem ){
|
|
|
493
519
|
function fvsProblem(
|
|
494
520
|
sTextPage, sTextQuestion, sTextAnswer = null, sTextPending = null,
|
|
495
521
|
sTextStatus = null, sTextHistory = null, sTextConflict = null,
|
|
496
|
-
sTextThoughts = null
|
|
522
|
+
sTextThoughts = null, sTextClaims = null
|
|
497
523
|
){
|
|
498
524
|
const aQuestionOfId = faQuestionOfId( sTextQuestion, sTextHistory )
|
|
499
525
|
const vsActiveQuestionId = faActiveQuestionIds( sTextQuestion )
|
|
@@ -527,6 +553,34 @@ function fvsProblem(
|
|
|
527
553
|
...( sTextThoughts === null ? [] : fvsParsed(
|
|
528
554
|
sFileThoughts, sTextThoughts, fvsThoughtsProblem
|
|
529
555
|
)),
|
|
556
|
+
...( sTextClaims === null ? [] : fvsParsed(
|
|
557
|
+
sFileClaims, sTextClaims, ( aClaims ) => {
|
|
558
|
+
const faIds = ( sText, sName ) => {
|
|
559
|
+
try {
|
|
560
|
+
const aData = JSON.parse( sText || ( sName === "thought" ? "[]" : "{}" ))
|
|
561
|
+
return new Set( sName === "conflict"
|
|
562
|
+
? Array.isArray( aData ) ? aData.map(( a ) => a && a.id ) : []
|
|
563
|
+
: sName === "thought"
|
|
564
|
+
? Array.isArray( aData ) ? aData.map(( a ) => a && a.id ) : []
|
|
565
|
+
: Object.keys( fbObject( aData ) ? aData : {} ))
|
|
566
|
+
} catch( xError ){ return new Set() }
|
|
567
|
+
}
|
|
568
|
+
return fvsClaimsProblem( aClaims, {
|
|
569
|
+
answer: new Set([
|
|
570
|
+
...( () => {
|
|
571
|
+
try {
|
|
572
|
+
const vaQuestion = JSON.parse( sTextQuestion || "[]" )
|
|
573
|
+
return Array.isArray( vaQuestion )
|
|
574
|
+
? vaQuestion.map(( a ) => a && a.id ) : []
|
|
575
|
+
} catch( xError ){ return [] }
|
|
576
|
+
})(),
|
|
577
|
+
...Object.keys( sTextAnswer === null ? {} : faAnswerOrEmpty( sTextAnswer )),
|
|
578
|
+
]),
|
|
579
|
+
thought: faIds( sTextThoughts, "thought" ),
|
|
580
|
+
conflict: faIds( sTextConflict, "conflict" ),
|
|
581
|
+
})
|
|
582
|
+
}
|
|
583
|
+
)),
|
|
530
584
|
]
|
|
531
585
|
}
|
|
532
586
|
|
|
@@ -535,4 +589,5 @@ module.exports = {
|
|
|
535
589
|
sFilePending, fsQuestionId, fsLegacyQuestionId, fsBranchHash, faQuestionOptions, sXOptionText,
|
|
536
590
|
sFileStatus, sFileHistory, sFileConflict, fvsStatusProblem, faQuestionOfId,
|
|
537
591
|
sFileThoughts, fvsThoughtsProblem, fsThoughtId,
|
|
592
|
+
sFileClaims, fvsClaimsProblem,
|
|
538
593
|
}
|
package/scripts/commands.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// @implements s-s1eg2z@e4283c0
|
|
6
6
|
// @implements s-hsle36@555b1d8
|
|
7
7
|
// @implements s-7wfrt9@cf88f5c
|
|
8
|
+
// @implements s-q3kge1@ba97ef3
|
|
8
9
|
// @implements s-854kw7@d393719
|
|
9
10
|
// @implements s-y1y75p@326b683
|
|
10
11
|
// @implements s-zrui8r@5a4422e
|
|
@@ -61,6 +62,10 @@
|
|
|
61
62
|
// @implements s-14wrxl@7a4474b
|
|
62
63
|
// @implements s-amd1ad@5c9c949
|
|
63
64
|
// @implements s-8hvj7z@4ee0664
|
|
65
|
+
// @implements s-2jdc1d@964ddde
|
|
66
|
+
// @implements s-ky6iv4@0879c7a
|
|
67
|
+
// @implements s-7pdsig@761907d
|
|
68
|
+
// @implements s-7uz5pq@ea5a63d
|
|
64
69
|
// @implements s-wapj39@5412047
|
|
65
70
|
// @implements s-twjecx@96950c4
|
|
66
71
|
// @implements s-g5gxep@784430f
|
|
@@ -73,7 +78,7 @@ const {
|
|
|
73
78
|
fvsProblem, fvsAddProblem, fbIsoTime, sFilePage, sFileQuestion, sFileAnswer,
|
|
74
79
|
sFilePending, fsQuestionId, fsLegacyQuestionId, fsBranchHash, faQuestionOptions,
|
|
75
80
|
sFileStatus, fvsStatusProblem,
|
|
76
|
-
sFileHistory, sFileConflict, faQuestionOfId, sFileThoughts,
|
|
81
|
+
sFileHistory, sFileConflict, faQuestionOfId, sFileThoughts, sFileClaims,
|
|
77
82
|
} = require( "./check" )
|
|
78
83
|
const { fxWithLock } = require( "./project-lock" )
|
|
79
84
|
const { fsHtml } = require( "./page-build" )
|
|
@@ -93,6 +98,8 @@ const sUsage = [
|
|
|
93
98
|
" idd import <page>",
|
|
94
99
|
" idd answer <id> (a JSON answer on stdin)",
|
|
95
100
|
" idd status <session-id> <blocked|working|clear>",
|
|
101
|
+
" idd claim <answer|thought|conflict> <item-id> <session-id>",
|
|
102
|
+
" idd release <answer|thought|conflict> <item-id> <session-id>",
|
|
96
103
|
" idd count [--nosync] [--type <c|u|a|s|conflicts|unanswered|answered|superseded>]",
|
|
97
104
|
" idd thoughts [--nosync]",
|
|
98
105
|
" idd clear-pending <id> <time>",
|
|
@@ -164,7 +171,7 @@ function aViewerUrl( project, vsArg ){
|
|
|
164
171
|
try {
|
|
165
172
|
uViewer = new URL(
|
|
166
173
|
process.env.IDD_VIEWER_URL ||
|
|
167
|
-
"https://
|
|
174
|
+
"https://interrogation.dev"
|
|
168
175
|
)
|
|
169
176
|
} catch( xError ){
|
|
170
177
|
return aResult( 2, "", "IDD_VIEWER_URL is not a valid URL\n" )
|
|
@@ -195,7 +202,7 @@ async function fOpenEventStream(){
|
|
|
195
202
|
try {
|
|
196
203
|
const uViewer = new URL(
|
|
197
204
|
process.env.IDD_VIEWER_URL ||
|
|
198
|
-
"https://
|
|
205
|
+
"https://interrogation.dev"
|
|
199
206
|
)
|
|
200
207
|
if( ![ "http:", "https:" ].includes( uViewer.protocol )) return null
|
|
201
208
|
uEvents = new URL( "/events", uViewer )
|
|
@@ -408,7 +415,7 @@ function fvsProblemOf( project ){
|
|
|
408
415
|
project.sReadOrNull( sFileAnswer ), project.sReadOrNull( sFilePending ),
|
|
409
416
|
project.sReadOrNull( sFileStatus ), project.sReadOrNull( sFileHistory ),
|
|
410
417
|
project.sReadOrNull( sFileConflict ),
|
|
411
|
-
project.sReadOrNull( sFileThoughts )
|
|
418
|
+
project.sReadOrNull( sFileThoughts ), project.sReadOrNull( sFileClaims )
|
|
412
419
|
)
|
|
413
420
|
}
|
|
414
421
|
|
|
@@ -645,6 +652,58 @@ function aStatus( project, vsArg ){
|
|
|
645
652
|
return aResult( 0 )
|
|
646
653
|
}
|
|
647
654
|
|
|
655
|
+
function aWorkClaim( project, vsArg, bRelease ){
|
|
656
|
+
const [ sKind, sItemId, sSessionId ] = vsArg
|
|
657
|
+
if( ![ "answer", "thought", "conflict" ].includes( sKind ) ||
|
|
658
|
+
!/^[0-9a-f]{64}$/.test( sItemId ) ||
|
|
659
|
+
!/^[A-Za-z0-9._:-]{1,128}$/.test( sSessionId )){
|
|
660
|
+
return aResult( 2, "", "claim kind, item id or session id is invalid\n" )
|
|
661
|
+
}
|
|
662
|
+
const vsProblem = fvsProblemOf( project )
|
|
663
|
+
if( vsProblem.length ) return aProblem( vsProblem )
|
|
664
|
+
const sTextClaims = project.sReadOrNull( sFileClaims )
|
|
665
|
+
const aClaims = sTextClaims === null ? {} : aParsedOrNull( sTextClaims )
|
|
666
|
+
if( !fbObject( aClaims )) return aResult( 1, "", sFileClaims + ": not a JSON object\n" )
|
|
667
|
+
const sKey = sKind + ":" + sItemId
|
|
668
|
+
const aExisting = aClaims[ sKey ]
|
|
669
|
+
if( bRelease ){
|
|
670
|
+
if( !aExisting ) return aResult( 0 )
|
|
671
|
+
if( aExisting.sessionId !== sSessionId ){
|
|
672
|
+
return aResult( 1, "", "work item is claimed by " + aExisting.sessionId + "\n" )
|
|
673
|
+
}
|
|
674
|
+
delete aClaims[ sKey ]
|
|
675
|
+
} else {
|
|
676
|
+
if( aExisting ){
|
|
677
|
+
if( aExisting.sessionId === sSessionId ) return aResult( 0 )
|
|
678
|
+
return aResult( 1, "", "work item is claimed by " + aExisting.sessionId + "\n" )
|
|
679
|
+
}
|
|
680
|
+
const aAvailableIds = sKind === "answer"
|
|
681
|
+
? faQuestionOfId( project.sReadOrNull( sFileQuestion ), project.sReadOrNull( sFileHistory ))
|
|
682
|
+
: aArrayIds( project.sReadOrNull( sKind === "thought" ? sFileThoughts : sFileConflict ))
|
|
683
|
+
if( !( sItemId in aAvailableIds ) && !aAvailableIds.has?.( sItemId )){
|
|
684
|
+
return aResult( 2, "", "item id is not present in its source file\n" )
|
|
685
|
+
}
|
|
686
|
+
aClaims[ sKey ] = { sessionId: sSessionId, claimedAt: new Date().toISOString() }
|
|
687
|
+
}
|
|
688
|
+
project.writeJson( sFileClaims, aClaims )
|
|
689
|
+
return aResult( 0 )
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function aArrayIds( sText ){
|
|
693
|
+
try {
|
|
694
|
+
const vaData = JSON.parse( sText || "[]" )
|
|
695
|
+
return new Set( Array.isArray( vaData ) ? vaData.map(( a ) => a && a.id ) : [] )
|
|
696
|
+
} catch( xError ){ return new Set() }
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function aClaim( project, vsArg ){
|
|
700
|
+
return aWorkClaim( project, vsArg, false )
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function aRelease( project, vsArg ){
|
|
704
|
+
return aWorkClaim( project, vsArg, true )
|
|
705
|
+
}
|
|
706
|
+
|
|
648
707
|
function aCount( project, vsArg ){
|
|
649
708
|
let bNoSync = false
|
|
650
709
|
let sType = null
|
|
@@ -802,6 +861,8 @@ const aSpec = {
|
|
|
802
861
|
"answer": { cArg: 1, fa: aAnswer, bNeedsPage: true, bWrites: true },
|
|
803
862
|
"clear-pending": { cArg: 2, fa: aClearPending, bNeedsPage: true, bWrites: true },
|
|
804
863
|
"status": { cArg: 2, fa: aStatus, bNeedsPage: true, bWrites: true },
|
|
864
|
+
"claim": { cArg: 3, fa: aClaim, bNeedsPage: true, bWrites: true },
|
|
865
|
+
"release": { cArg: 3, fa: aRelease, bNeedsPage: true, bWrites: true },
|
|
805
866
|
}
|
|
806
867
|
|
|
807
868
|
function aRun( sRoot, vsArg, fsStdin = () => "" ){
|
package/specs/build.md
CHANGED
|
@@ -11,10 +11,14 @@ invokes; no fixer runs `node`, `jest` or an ad-hoc pipeline outside these.
|
|
|
11
11
|
|
|
12
12
|
* {#g-94bsyg <arch for=main,architect,fixer via=prompt} Use Node >= 20 and plain
|
|
13
13
|
JavaScript with CommonJS modules (`require` / `module.exports`) for the
|
|
14
|
-
command and
|
|
14
|
+
command and server. The browser client uses Preact and is bundled into
|
|
15
|
+
same-origin static assets.
|
|
15
16
|
* {#g-d4bbao <arch for=main,architect,fixer via=prompt} Keep Jest at major version
|
|
16
17
|
29, and test the page in a DOM emulated by `jsdom`, with the page's store
|
|
17
18
|
and comment services replaced by fakes the test controls.
|
|
19
|
+
* {#s-0bt8j8 <arch} The browser client is rendered with Preact and emitted as a
|
|
20
|
+
same-origin static asset by `npm run build:web`; `npm test` and
|
|
21
|
+
`npm run deploy` build that asset before use.
|
|
18
22
|
* {#g-kv15zk <arch for=main,architect,fixer via=prompt} Keep this an
|
|
19
23
|
interpreted-language project. Developers install its dependencies with
|
|
20
24
|
`npm install`; adopters add the public package to their project with
|
|
@@ -67,6 +71,10 @@ invokes; no fixer runs `node`, `jest` or an ad-hoc pipeline outside these.
|
|
|
67
71
|
`@google-cloud/firestore` to retain MCP event subscriptions across Cloud Run
|
|
68
72
|
restarts; production supplies a Firestore database and grants the Cloud Run
|
|
69
73
|
service account Firestore read/write access.
|
|
74
|
+
* {#g-vk03sm <arch for=main,architect,fixer via=prompt} The hosted service
|
|
75
|
+
uses Firestore for per-GitHub-user theme preferences as well as MCP event
|
|
76
|
+
subscriptions; user theme records use a separate collection and do not store
|
|
77
|
+
access tokens or answer data.
|
|
70
78
|
* {#g-u68buh <arch for=main,architect,fixer via=prompt} Keep
|
|
71
79
|
`@google-cloud/firestore` and `standardwebhooks` in the hosted workspace's
|
|
72
80
|
runtime dependencies and
|
|
@@ -118,6 +126,23 @@ invokes; no fixer runs `node`, `jest` or an ad-hoc pipeline outside these.
|
|
|
118
126
|
web application, its runtime dependencies, Dockerfile and Cloud Run deployment
|
|
119
127
|
script. The hosted service has a separate workspace manifest and is deployed
|
|
120
128
|
from the IDD repository; it is reached at `https://interrogation.dev`.
|
|
129
|
+
> {#m-r4balx} RULING (CEO), 2026-11-12: “yes, let's do that.” This approves
|
|
130
|
+
> GitHub Actions trusted publishing so package releases do not require the
|
|
131
|
+
> maintainer to supply a one-time code for each release.
|
|
132
|
+
* {#s-jm0nq3 <m-r4balx@e53a626} The root package metadata names `https://github.com/debonet/idd`
|
|
133
|
+
as its repository, matching the source repository used by npm trusted
|
|
134
|
+
publishing.
|
|
135
|
+
* {#s-k9c49i <m-r4balx@e53a626} `.github/workflows/publish.yml` publishes only version tags. It
|
|
136
|
+
checks out the source, installs the supported Node and npm versions, runs the
|
|
137
|
+
full test suite, and publishes `@debonet/idd` with `id-token: write` and
|
|
138
|
+
`contents: read`; it stores no npm token.
|
|
139
|
+
* {#s-r6qcwv <m-r4balx@e53a626} Configure npm's trusted publisher for `debonet/idd` and the
|
|
140
|
+
`publish.yml` workflow, allowing direct package publishing. A release tag's
|
|
141
|
+
version matches `package.json`; the workflow uses npm OIDC and publishes
|
|
142
|
+
provenance without an npm access token.
|
|
143
|
+
* {#i-re3pab <s-r6qcwv@8f29404} **Do:** Run `npm trust list @debonet/idd`.
|
|
144
|
+
**Look for:** GitHub Actions trusted publishing for `debonet/idd` with
|
|
145
|
+
`publish.yml` and direct publishing allowed.
|
|
121
146
|
* {#g-aiemo2 <arch for=main,architect,fixer via=prompt} Publish the package
|
|
122
147
|
publicly as `@debonet/idd`, exposing `idd` and `idd-merge-hook` from
|
|
123
148
|
their matching files under `bin/`, so adopters can install it from npm.
|
package/specs/guidance/codex.md
CHANGED
|
@@ -76,6 +76,14 @@ incorporated. Each `[o-…]` citation names an entry of
|
|
|
76
76
|
thing preventing progress; use `working` as soon as work can proceed, and
|
|
77
77
|
`clear` before ending the session. Commit and push each status change so
|
|
78
78
|
the viewer can show it.
|
|
79
|
+
* {#g-bd17rs <s-7ppa6w@305659d for=main via=prompt} **Listen for hosted changes.**
|
|
80
|
+
Configure the IDD MCP server once with
|
|
81
|
+
`codex mcp add idd --url https://interrogation.dev/mcp`, then restart Codex.
|
|
82
|
+
While waiting on a hosted answer, keep both the MCP
|
|
83
|
+
`idd_wait_for_change` tool and `npm exec -- idd wait <question-id>` active.
|
|
84
|
+
When either reports a change, sync the matching IDD branch and inspect its
|
|
85
|
+
latest answers, thoughts and conflicts. Treat a second signal for the same
|
|
86
|
+
branch update as a duplicate; incorporate an answer only once.
|
|
79
87
|
|
|
80
88
|
## Dependencies
|
|
81
89
|
|
|
@@ -57,6 +57,12 @@ answers are pending. Each `[o-…]` citation names an entry of
|
|
|
57
57
|
> C) The session names q201 and asks whether to act on it now." His answer:
|
|
58
58
|
> A.
|
|
59
59
|
|
|
60
|
+
> {#m-kh7vgm} THOUGHT (CEO), 2026-11-13: “We need to make sure that
|
|
61
|
+
> when a question is answered (or observation given or conflict resolved.)
|
|
62
|
+
> that only one active agent picks up on that work. Which means that when
|
|
63
|
+
> agents pull a new chunk of work, they need to write back to the Jason file
|
|
64
|
+
> and check it in indicating that they are doing that work.”
|
|
65
|
+
|
|
60
66
|
## Starting a session
|
|
61
67
|
|
|
62
68
|
* {#g-3ncq2t <m-vmsvl8@415d9d6 for=main via=prompt} **Find the matching IDD branch first.** At
|
|
@@ -92,6 +98,15 @@ answers are pending. Each `[o-…]` citation names an entry of
|
|
|
92
98
|
answer. Work on it when it applies and is not already reflected in the
|
|
93
99
|
project; if it needs a decision, ask a follow-up through the normal IDD
|
|
94
100
|
question flow. Do not rewrite or remove submitted thoughts.
|
|
101
|
+
* {#g-mfqz8n <m-kh7vgm@5b68e68 for=main via=prompt} **Claim shared work before acting.**
|
|
102
|
+
Before incorporating a pending answer or acting on a submitted thought or
|
|
103
|
+
answer conflict, claim that item's stable id for this session with
|
|
104
|
+
`npm exec -- idd claim <kind> <item-id> <session-id>`. Commit and push
|
|
105
|
+
`idd/claims.json` to the matching IDD branch before starting the work. If a
|
|
106
|
+
push is rejected, sync the branch and inspect the claim; do not start work
|
|
107
|
+
unless this session owns the claim. After the work is incorporated, release
|
|
108
|
+
the claim with `npm exec -- idd release <kind> <item-id> <session-id>`, then
|
|
109
|
+
commit and push the release.
|
|
95
110
|
|
|
96
111
|
## Every decision goes through the tool's questionnaire
|
|
97
112
|
|
|
@@ -87,6 +87,28 @@ projects in one viewer.
|
|
|
87
87
|
> access to more repositories. I think I may have only approved the one
|
|
88
88
|
> project (idd) at this point.”
|
|
89
89
|
|
|
90
|
+
> {#m-babliv} BUG REPORT (CEO), 2026-11-13: “OMG. Sometimes the text
|
|
91
|
+
> field gets erased while I'm typing in it. What is happening? Is the page
|
|
92
|
+
> refreshing and wiping out my in-progress work?”
|
|
93
|
+
|
|
94
|
+
> {#m-zqh3c0} BUG REPORT (CEO), 2026-11-13: “Importantly also: the page
|
|
95
|
+
> scroll should not change when being updated. It is quite jarring.”
|
|
96
|
+
|
|
97
|
+
> {#m-6nlxee} BUG REPORT (CEO), 2026-11-13: “Better, but the text I was
|
|
98
|
+
> editing scrolled when the page updated (unsure if my caret moved, but the
|
|
99
|
+
> scroll of the textbox went back to 0 and I could not see the bottom/most
|
|
100
|
+
> recent part of the entry)”
|
|
101
|
+
|
|
102
|
+
> {#m-mwyvc9} RULING (CEO), 2026-11-13: “Yes. Do it. Each item already has a
|
|
103
|
+
> stable id, so use that for the root of the element key”
|
|
104
|
+
|
|
105
|
+
> {#m-sv3g4i} BUG REPORT (CEO), 2026-11-13: “when you sign in using
|
|
106
|
+
> the menu, the menu should go away. Today it remains open.”
|
|
107
|
+
|
|
108
|
+
> {#m-3qt6qa} “I'm still seeing
|
|
109
|
+
> submitted observations in the observations box. They should be moved to the
|
|
110
|
+
> answered section.”
|
|
111
|
+
|
|
90
112
|
> {#m-h21kou} RULING (CEO), 2026-11-10: “Also, don't show the branch in the
|
|
91
113
|
> special case where it is \"main\"
|
|
92
114
|
>
|
|
@@ -387,6 +409,19 @@ projects in one viewer.
|
|
|
387
409
|
automatically detects changes to IDD branches and refreshes their displayed
|
|
388
410
|
questionnaire data without requiring a page reload. It preserves unsent
|
|
389
411
|
answer drafts and the current dashboard or questionnaire view.
|
|
412
|
+
* {#s-o1emm0 <m-babliv@56a9a30} A background questionnaire refresh preserves the
|
|
413
|
+
unsent text in the Thoughts and observations field, as well as unsent answer
|
|
414
|
+
drafts and the current page view.
|
|
415
|
+
* {#s-nedtaj <m-zqh3c0@ebc8c99} When a background data refresh redraws the dashboard or
|
|
416
|
+
questionnaire, the page retains its scroll position.
|
|
417
|
+
* {#s-b7mfmz <m-6nlxee@8de6a06} When a background questionnaire refresh redraws a text
|
|
418
|
+
field, it restores that field's internal scroll position, selection and
|
|
419
|
+
focus, so the person can continue editing where they left off.
|
|
420
|
+
* {#s-0ez7w1 <m-mwyvc9@7e88d5d} A questionnaire item that remains present across a background refresh or
|
|
421
|
+
reorder retains its rendered root and any in-progress editing state; stable
|
|
422
|
+
item ids distinguish questions, thoughts, answers and conflicts.
|
|
423
|
+
* {#s-np9p6t <m-sv3g4i@5b705c0} After GitHub sign-in succeeds, the navigation menu
|
|
424
|
+
closes.
|
|
390
425
|
* {#s-1v9p2k <m-c37rfq@5dbe195} The GitHub App's push webhook is validated with
|
|
391
426
|
its configured HMAC secret. Only pushes to refs beginning
|
|
392
427
|
`refs/heads/idd-` trigger a viewer notification; webhook payloads are not
|
|
@@ -415,6 +450,10 @@ projects in one viewer.
|
|
|
415
450
|
* {#s-jzlic8 <arch} The `/mcp` endpoint exposes the standard MCP tool
|
|
416
451
|
`idd_wait_for_change`; an active call waits up to 240 seconds for an IDD
|
|
417
452
|
branch push and returns only whether a change occurred.
|
|
453
|
+
* {#s-7ppa6w <arch} A Codex session may wait for changes through both the IDD
|
|
454
|
+
MCP `idd_wait_for_change` tool and `idd wait <question-id>` at once. Either
|
|
455
|
+
can signal a change; the session synchronizes and inspects the latest IDD
|
|
456
|
+
branch, treating duplicate signals idempotently.
|
|
418
457
|
* {#s-udvxd8 <arch} The hosted service stores MCP event subscriptions durably,
|
|
419
458
|
including their callback URL, signing secret, event name, and expiration,
|
|
420
459
|
and retains them across Cloud Run restarts.
|
|
@@ -472,9 +511,10 @@ projects in one viewer.
|
|
|
472
511
|
idea or observation. Submitting non-empty text appends a thought to that
|
|
473
512
|
project's current IDD branch; the form remains available for another
|
|
474
513
|
submission.
|
|
475
|
-
* {#s-zexwsh <
|
|
476
|
-
|
|
477
|
-
|
|
514
|
+
* {#s-zexwsh <m-yxktyi@b0efeab,m-3qt6qa@4b64c8f} Immediately after a thought is submitted successfully, its card appears in the Answered section with its
|
|
515
|
+
stable id, submission time and text as plain text. The Thoughts and
|
|
516
|
+
observations section holds only the submission form. Thought text is never
|
|
517
|
+
displayed as HTML or treated as a question requiring an answer.
|
|
478
518
|
* {#s-pla1x4 <arch} Submitting a thought writes directly from the browser to the
|
|
479
519
|
repository's `idd/thoughts.json` on its IDD branch using GitHub access
|
|
480
520
|
already granted to the signed-in user. A user without repository write
|
|
@@ -524,6 +564,15 @@ projects in one viewer.
|
|
|
524
564
|
`https://interrogation.dev`, install `@debonet/idd`, resolve the installed
|
|
525
565
|
package's `INSTRUCTIONS.md` path with `require.resolve`, and follow those
|
|
526
566
|
instructions to set up IDD in the current project.
|
|
567
|
+
> {#m-2kt5yx} FEATURE REQUEST (CEO), 2026-11-12: “The installation instructions are not
|
|
568
|
+
> visible on interrogation.dev Are they there? (actually, hiding them in an
|
|
569
|
+
> agent-readable comoment early in the file might be a great idea, e.g.:
|
|
570
|
+
> <!-- if you're an agent that wants to install Interrogation Driven Development
|
|
571
|
+
> (IDD), use npm install @debonet/idd --> )”
|
|
572
|
+
* {#s-iy7nbz <m-2kt5yx@1aed464} The public landing page's HTML starts with a plain HTML comment
|
|
573
|
+
telling agents to install `@debonet/idd`, read its installed
|
|
574
|
+
`INSTRUCTIONS.md`, and use `https://interrogation.dev`; the comment is not
|
|
575
|
+
displayed as page text.
|
|
527
576
|
* {#s-nq82ty <arch} The viewer signs users in through the IDD
|
|
528
577
|
GitHub App's user authorization flow and requests only the app's repository
|
|
529
578
|
contents permission.
|
|
@@ -657,6 +706,12 @@ projects in one viewer.
|
|
|
657
706
|
* {#s-omsgq8 <arch} `idd/status.json` is an object keyed by active agent session
|
|
658
707
|
id; each value has exactly a boolean `blocked` field and an ISO 8601
|
|
659
708
|
`updatedAt` field.
|
|
709
|
+
* {#s-0u0b06 <arch} If `idd/claims.json` exists, it is an object keyed by
|
|
710
|
+
`<kind>:<item-id>` where kind is `answer`, `thought` or `conflict` and the
|
|
711
|
+
item id is a lowercase 64-character hexadecimal id. Each value has exactly
|
|
712
|
+
a valid `sessionId` and an ISO 8601 `claimedAt` field. A claim may name only
|
|
713
|
+
an answer's question id, a submitted thought id, or a conflict id present in
|
|
714
|
+
the corresponding IDD file.
|
|
660
715
|
* {#s-w9knti <arch} An agent session updates its entry on the
|
|
661
716
|
matching IDD branch when its blocked state changes, sets `blocked` true
|
|
662
717
|
only when it cannot continue without a human answer, refreshes `updatedAt`
|
package/specs/idd-page.md
CHANGED
|
@@ -9,7 +9,8 @@ page file and question file, checks them, builds the questions page from
|
|
|
9
9
|
them, adds questions with branch-scoped hash ids, records and clears answers
|
|
10
10
|
in the project's answer and pending answer files, and imports questions
|
|
11
11
|
from a page made from the older template; it also reports session status,
|
|
12
|
-
prints a hosted viewer link, and
|
|
12
|
+
claims work items between agent sessions, prints a hosted viewer link, and
|
|
13
|
+
counts questions on an IDD branch.
|
|
13
14
|
|
|
14
15
|
## His words
|
|
15
16
|
|
|
@@ -43,6 +44,14 @@ prints a hosted viewer link, and counts questions on an IDD branch.
|
|
|
43
44
|
encodes both values and uses `IDD_VIEWER_URL` when set, otherwise the
|
|
44
45
|
package's configured public viewer address. It does not read a GitHub
|
|
45
46
|
credential or change files.
|
|
47
|
+
> {#m-umjnwb} BUG REPORT (CEO), 2026-11-13: “Somehow the new agent got this for a
|
|
48
|
+
> login page: https://idd-service-778162731919.us-east4.run.app/?repo=AblifyAI%2Fablify&branch=main
|
|
49
|
+
> It should be using interrogation.dev everywhere (that does not work with
|
|
50
|
+
> github sign in!)”
|
|
51
|
+
* {#s-q3kge1 <m-umjnwb@4a707e9} When `IDD_VIEWER_URL` is unset, both `viewer-url` and `wait` use
|
|
52
|
+
`https://interrogation.dev` as their origin. They never use the Cloud Run
|
|
53
|
+
`run.app` address as a viewer link or event endpoint, so GitHub sign-in stays
|
|
54
|
+
on the registered custom domain.
|
|
46
55
|
* {#s-dal2u7 <arch} **`wait <question-id> [interval-seconds]`** subscribes to
|
|
47
56
|
the hosted viewer's event stream and checks the matching remote IDD branch
|
|
48
57
|
through the current worktree's configured Git remote when the wait starts
|
|
@@ -84,6 +93,24 @@ prints a hosted viewer link, and counts questions on an IDD branch.
|
|
|
84
93
|
every entry by `specs/hosted-questionnaire.md`. Clearing an absent entry
|
|
85
94
|
succeeds without writing the file.
|
|
86
95
|
|
|
96
|
+
## Work claims
|
|
97
|
+
|
|
98
|
+
* {#s-2jdc1d} **`claim <kind> <item-id> <session-id>`** records that one active
|
|
99
|
+
session owns an answer, thought or conflict work item in `idd/claims.json`.
|
|
100
|
+
It refuses to replace a claim held by another session and reports the
|
|
101
|
+
current owner; repeating a claim by its owner succeeds without changing its
|
|
102
|
+
timestamp.
|
|
103
|
+
* {#s-ky6iv4} **`release <kind> <item-id> <session-id>`** removes a claim only
|
|
104
|
+
when the named session owns it. Releasing an absent claim succeeds; a
|
|
105
|
+
different owner is reported and the file is unchanged.
|
|
106
|
+
* {#s-7pdsig} A claim kind is `answer`, `thought` or `conflict`; an
|
|
107
|
+
item id is a lowercase 64-character hexadecimal id; and a session id follows
|
|
108
|
+
the status command's validation rule. Invalid arguments exit 2. Malformed
|
|
109
|
+
claim data is refused without changing the file and exits 1.
|
|
110
|
+
* {#s-7uz5pq} Claiming authorizes work only after the claim has been
|
|
111
|
+
committed and pushed to the matching IDD branch. A rejected push means the
|
|
112
|
+
session must sync and inspect the remote claim before it starts work.
|
|
113
|
+
|
|
87
114
|
## count
|
|
88
115
|
|
|
89
116
|
* {#s-vwase3 <m-xoqfn9@0df3826} **`count`** reports the counts of conflicting, unanswered,
|
package/specs/project-files.md
CHANGED
|
@@ -5,9 +5,9 @@ Statement ids: complete
|
|
|
5
5
|
## Purpose
|
|
6
6
|
|
|
7
7
|
The files on an adopting project's matching IDD branch that hold its shared
|
|
8
|
-
questions, answers and
|
|
9
|
-
from the same question list and recorded results, while each tool
|
|
10
|
-
its own questionnaire.
|
|
8
|
+
questions, answers, pending answers and work claims, so sessions of any agent
|
|
9
|
+
tool work from the same question list and recorded results, while each tool
|
|
10
|
+
presents its own questionnaire.
|
|
11
11
|
|
|
12
12
|
## His words
|
|
13
13
|
|
|
@@ -161,6 +161,16 @@ its own questionnaire.
|
|
|
161
161
|
* {#s-u52jq6 <arch} **Absent means none pending.** A project with no pending answer file has
|
|
162
162
|
no locally pending answer; recording the first answer creates it.
|
|
163
163
|
|
|
164
|
+
## Work claims
|
|
165
|
+
|
|
166
|
+
* {#s-8ffele <arch} `idd/claims.json` holds one JSON object keyed by
|
|
167
|
+
`<kind>:<item-id>`. Each value has exactly `sessionId`, the owning agent
|
|
168
|
+
session id, and `claimedAt`, an ISO 8601 time. Kinds are `answer`, `thought`
|
|
169
|
+
and `conflict`; the item id is the stable 64-character id from its source
|
|
170
|
+
file.
|
|
171
|
+
* {#s-upxu0m <arch} If `idd/claims.json` is absent, no work item is claimed;
|
|
172
|
+
the first claim creates it.
|
|
173
|
+
|
|
164
174
|
## Thoughts and observations
|
|
165
175
|
|
|
166
176
|
* {#s-61lmfb <arch} `idd/thoughts.json` holds one JSON array of thoughts in append
|
package/specs/themes.md
CHANGED
|
@@ -31,6 +31,11 @@ per-project theme preference.
|
|
|
31
31
|
> or should match the splash page. Either way, the splash page theme should be
|
|
32
32
|
> a theming option.”
|
|
33
33
|
|
|
34
|
+
> {#m-rakcji} THOUGHT (CEO), 2026-11-13: “Theme choice should be shared
|
|
35
|
+
> across browsers, but not across users. Perhaps we should maintain a list of
|
|
36
|
+
> <user>,<theme> in the idd-<branch> directory on github, unless there is
|
|
37
|
+
> another way (e.g. is there github-user storage offered?)”
|
|
38
|
+
|
|
34
39
|
## Built-in themes
|
|
35
40
|
|
|
36
41
|
* {#s-ncz9te <m-qqilfo@d782597} The viewer offers the light, dark, retro, silk, and editorial
|
|
@@ -64,9 +69,24 @@ per-project theme preference.
|
|
|
64
69
|
## Theme choice and content hooks
|
|
65
70
|
|
|
66
71
|
* {#s-6bc09d <m-qqilfo@d782597} The questionnaire page lets the user choose a theme for
|
|
67
|
-
that project and code branch. The viewer saves the choice
|
|
68
|
-
|
|
69
|
-
the user returns to the questionnaire.
|
|
72
|
+
that project and code branch. The viewer saves the choice under the
|
|
73
|
+
authenticated GitHub user, repository and branch identity, and keeps that
|
|
74
|
+
preference when the user returns to the questionnaire in another browser.
|
|
75
|
+
* {#s-jl9nzo <m-rakcji@fd6be8a} A project theme preference is keyed to the
|
|
76
|
+
authenticated GitHub user's immutable account id and the repository and
|
|
77
|
+
branch identity; two users may choose different themes for the same project.
|
|
78
|
+
The splash, dashboard and project theme choices are stored in Firestore and
|
|
79
|
+
shared across browsers for that user. The hosted service does not expose one
|
|
80
|
+
user's preferences to another user.
|
|
81
|
+
* {#s-wmmbpw <m-rakcji@fd6be8a} The browser reads and writes the signed-in user's
|
|
82
|
+
theme choices through `/api/theme-preferences`, authenticating with its
|
|
83
|
+
in-memory GitHub access token. The service verifies the token's GitHub user
|
|
84
|
+
id before reading or writing that user's preference record. Theme preference
|
|
85
|
+
data contains only theme ids and project/repository branch keys;
|
|
86
|
+
it contains no access tokens or answers.
|
|
87
|
+
* {#s-6io4k8 <arch} Before sign-in, the splash page uses a browser-local
|
|
88
|
+
theme choice. After sign-in, the user's saved splash theme replaces that
|
|
89
|
+
browser-local choice.
|
|
70
90
|
* {#s-eydwk6 <m-fjqj3t@17d6d3e} The splash page and dashboard each have an independent built-in theme
|
|
71
91
|
selector in the content area directly below the site header; changing either
|
|
72
92
|
site theme leaves the other surface's theme and each project's questionnaire
|