devmethod-ai 0.1.0-rc.2 → 0.2.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/.agents/skills/decision-architecture/SKILL.md +15 -13
- package/.agents/skills/decision-architecture/assets/ADR.md +18 -17
- package/.agents/skills/decision-architecture/references/api-contracts.md +21 -16
- package/.agents/skills/decision-architecture/references/backend-boundaries.md +21 -18
- package/.agents/skills/decision-architecture/references/product-decisions.md +14 -17
- package/.agents/skills/design-to-code/SKILL.md +18 -11
- package/.agents/skills/design-to-code/assets/UI_ACCEPTANCE.md +13 -12
- package/.agents/skills/design-to-code/references/ux-contract.md +20 -15
- package/.agents/skills/design-to-code/references/visual-creation.md +37 -0
- package/.agents/skills/project-foundation/SKILL.md +39 -30
- package/.agents/skills/project-foundation/assets/AGENTS.foundation.md +16 -15
- package/.agents/skills/project-foundation/assets/PROJECT_PROFILE.md +24 -23
- package/.agents/skills/project-foundation/assets/START_HERE.md +17 -14
- package/.agents/skills/project-foundation/references/mission-context.md +30 -0
- package/.agents/skills/project-foundation/references/operating-commands.md +49 -34
- package/.agents/skills/project-foundation/references/profiles/README.md +18 -0
- package/.agents/skills/project-foundation/references/profiles/ci.md +11 -0
- package/.agents/skills/project-foundation/references/profiles/cloud-delivery.md +13 -0
- package/.agents/skills/project-foundation/references/profiles/messaging.md +11 -0
- package/.agents/skills/project-foundation/references/profiles/mongodb.md +11 -0
- package/.agents/skills/project-foundation/references/profiles/node-nest.md +11 -0
- package/.agents/skills/project-foundation/references/profiles/postgres-drizzle.md +11 -0
- package/.agents/skills/project-foundation/references/profiles/react-next.md +11 -0
- package/.agents/skills/project-foundation/references/profiles/typescript.md +11 -0
- package/.agents/skills/project-foundation/references/work-sizing.md +31 -0
- package/.agents/skills/react-feature-engineering/SKILL.md +26 -22
- package/.agents/skills/react-feature-engineering/references/review-and-sources.md +41 -37
- package/.agents/skills/reliable-ai-integration/SKILL.md +13 -10
- package/.agents/skills/reliable-ai-integration/assets/AI_EVALUATION.md +19 -18
- package/.agents/skills/reliable-ai-integration/references/evidence-and-media.md +22 -18
- package/.agents/skills/reliable-ai-integration/references/jobs-and-costs.md +20 -15
- package/.agents/skills/scoped-delivery/SKILL.md +25 -18
- package/.agents/skills/scoped-delivery/assets/CHECKPOINT.md +22 -11
- package/.agents/skills/scoped-delivery/assets/MISSION.md +35 -0
- package/.agents/skills/scoped-delivery/assets/SLICE.md +15 -14
- package/.agents/skills/scoped-delivery/assets/VERIFICATION.md +19 -0
- package/.agents/skills/scoped-delivery/references/verification-and-cost.md +21 -18
- package/COMPATIBILITY.md +3 -3
- package/CONTRIBUTING.md +13 -0
- package/README.md +94 -9
- package/START_HERE.md +24 -0
- package/VALIDATION.md +37 -0
- package/dist/checkpoint.js +171 -0
- package/dist/cli.js +118 -17
- package/dist/doctor.js +113 -0
- package/dist/filesystem.js +32 -0
- package/dist/init.js +38 -34
- package/dist/mission.js +85 -0
- package/dist/planner.js +65 -0
- package/dist/records.js +76 -0
- package/dist/update.js +57 -0
- package/docs/ADR-001-distribution.md +13 -0
- package/docs/ADR-002-read-only-diagnostics.md +25 -0
- package/docs/ADR-003-installation-provenance.md +15 -0
- package/docs/ADR-004-checkpoint-resumption.md +23 -0
- package/docs/ADR-005-mission-context.md +11 -0
- package/docs/ADR-006-opt-in-native-pilot.md +15 -0
- package/docs/BMAD-COMPARISON.md +34 -0
- package/docs/CODEX-ADAPTER-VALIDATION.md +20 -0
- package/docs/COMPARISON-ITERATION.md +25 -0
- package/docs/DOCTOR.md +23 -0
- package/docs/EVALUATION.md +39 -0
- package/docs/EVOLUTION.md +34 -0
- package/docs/GREENFIELD-VALIDATION.md +36 -0
- package/docs/MILESTONES.md +39 -0
- package/docs/MISSIONS.md +47 -0
- package/docs/NATIVE-PILOT-RESULTS.md +22 -0
- package/docs/NATIVE-PILOT.md +17 -0
- package/docs/ORCHESTRATION.md +11 -0
- package/docs/RC2-VALIDATION.md +50 -0
- package/docs/RELEASE-0.1.0.md +60 -0
- package/docs/RELEASE-0.2.0.md +37 -0
- package/docs/RELEASE-CHECKLIST.md +13 -0
- package/docs/RELEASE-NOTES-0.1.0-rc.2.md +32 -0
- package/docs/RELEASE-NOTES-0.1.0.md +11 -0
- package/docs/RESUMPTION.md +87 -0
- package/docs/ROADMAP.md +39 -0
- package/docs/STACK-PROFILES.md +9 -0
- package/docs/TROUBLESHOOTING.md +16 -0
- package/docs/UPDATES.md +30 -0
- package/docs/VISUAL-CREATION-PROPOSAL.md +21 -0
- package/docs/VISUAL-WORKFLOW.md +75 -0
- package/docs/images/devmethod-delivery.svg +10 -0
- package/docs/images/devmethod-flow.svg +13 -0
- package/docs/media/from-zero/README.md +17 -0
- package/docs/media/from-zero/devmethod-demo.fr.srt +59 -0
- package/docs/media/from-zero/scenes.json +50 -0
- package/docs/media/full-chain-4k/README.md +37 -0
- package/docs/media/full-chain-4k/devmethod-chaine-complete.fr.srt +435 -0
- package/docs/media/full-chain-4k/scenes.json +173 -0
- package/docs/media/full-chain-4k/transcripts.fr.md +315 -0
- package/docs/media/visual-chain/README.md +44 -0
- package/docs/media/visual-chain/assets/add-book-v1.png +0 -0
- package/docs/media/visual-chain/assets/completed-v1.png +0 -0
- package/docs/media/visual-chain/devmethod-du-besoin-au-produit.fr.srt +147 -0
- package/docs/media/visual-chain/execution.fr.md +30 -0
- package/docs/media/visual-chain/image-prompts.json +9 -0
- package/docs/media/visual-chain/reference-hashes.json +5 -0
- package/docs/media/visual-chain/scenes.json +232 -0
- package/docs/media/visual-chain/video-preview.jpg +0 -0
- package/docs/missions/visual-workflow.md +46 -0
- package/evaluation/COMPARISONS.md +33 -0
- package/evaluation/README.md +35 -0
- package/evaluation/candidate-setup.json +33 -0
- package/evaluation/comparison-v2-readiness.json +18 -0
- package/evaluation/comparison-v2-results.json +58 -0
- package/evaluation/fixtures/dependency/CONTRIBUTING.md +3 -0
- package/evaluation/fixtures/dependency/TICKET.md +3 -0
- package/evaluation/fixtures/dependency/acceptance.test.mjs +4 -0
- package/evaluation/fixtures/dependency/api.mjs +1 -0
- package/evaluation/fixtures/failing-verification/CONTRIBUTING.md +3 -0
- package/evaluation/fixtures/failing-verification/acceptance.test.mjs +4 -0
- package/evaluation/fixtures/failing-verification/total.mjs +1 -0
- package/evaluation/fixtures/stale-handoff/CHECKPOINT.md +3 -0
- package/evaluation/fixtures/stale-handoff/CONTRACT.md +3 -0
- package/evaluation/fixtures/stale-handoff/CONTRIBUTING.md +3 -0
- package/evaluation/fixtures/stale-handoff/acceptance.test.mjs +4 -0
- package/evaluation/fixtures/stale-handoff/label.mjs +1 -0
- package/evaluation/fixtures/update-customization/README.md +13 -0
- package/evaluation/fixtures.json +85 -0
- package/evaluation/greenfield/acceptance.test.mjs +57 -0
- package/evaluation/greenfield/security.test.mjs +28 -0
- package/evaluation/native-pilot-results.json +155 -0
- package/examples/README.md +36 -0
- package/examples/bugfix/acceptance.test.mjs +17 -0
- package/examples/bugfix/page-size.mjs +4 -0
- package/examples/clair-from-zero/AGENT-EVALUATION.md +15 -0
- package/examples/clair-from-zero/MISSION.md +41 -0
- package/examples/clair-from-zero/README.md +24 -0
- package/examples/clair-from-zero/app/app.mjs +69 -0
- package/examples/clair-from-zero/app/domain.mjs +30 -0
- package/examples/clair-from-zero/app/index.html +20 -0
- package/examples/clair-from-zero/app/storage.mjs +4 -0
- package/examples/clair-from-zero/app/styles.css +128 -0
- package/examples/clair-from-zero/browser-check.cjs +2 -0
- package/examples/clair-from-zero/tests/domain.test.mjs +37 -0
- package/examples/fullstack/CONTRACT.md +23 -0
- package/examples/fullstack/DEPENDENCIES.md +18 -0
- package/examples/fullstack/README.md +100 -0
- package/examples/fullstack/api/application/tasks.ts +13 -0
- package/examples/fullstack/api/domain/task.ts +12 -0
- package/examples/fullstack/api/http.ts +26 -0
- package/examples/fullstack/api/infrastructure/migrate.ts +15 -0
- package/examples/fullstack/api/infrastructure/postgres.ts +18 -0
- package/examples/fullstack/api/infrastructure/schema.ts +6 -0
- package/examples/fullstack/api/main.ts +17 -0
- package/examples/fullstack/api/tsconfig.json +17 -0
- package/examples/fullstack/compose.yaml +14 -0
- package/examples/fullstack/migrations/0000_tasks.sql +5 -0
- package/examples/fullstack/migrations/meta/_journal.json +13 -0
- package/examples/fullstack/mission.json +34 -0
- package/examples/fullstack/package-lock.json +2661 -0
- package/examples/fullstack/package.json +45 -0
- package/examples/fullstack/tests/e2e.test.cjs +54 -0
- package/examples/fullstack/tests/http.test.cjs +33 -0
- package/examples/fullstack/tests/postgres.test.cjs +29 -0
- package/examples/fullstack/tests/unit.test.cjs +20 -0
- package/examples/fullstack/tests/web-model.test.cjs +23 -0
- package/examples/fullstack/web/app/layout.tsx +4 -0
- package/examples/fullstack/web/app/page.tsx +11 -0
- package/examples/fullstack/web/features/tasks/components/task-list.tsx +11 -0
- package/examples/fullstack/web/features/tasks/hooks/use-task-filter.ts +7 -0
- package/examples/fullstack/web/features/tasks/model/tasks.ts +12 -0
- package/examples/fullstack/web/features/tasks/server/list-tasks.ts +8 -0
- package/examples/fullstack/web/next.config.mjs +2 -0
- package/examples/fullstack/web/tsconfig.json +36 -0
- package/examples/mission/CONTRACT.md +3 -0
- package/examples/mission/mission.json +18 -0
- package/examples/mission/plan.json +11 -0
- package/examples/pocket-tasks/BRIEF.md +21 -0
- package/examples/pocket-tasks/PROJECT_PROFILE.md +19 -0
- package/examples/pocket-tasks/README.md +43 -0
- package/examples/pocket-tasks/docs/ADR-001.md +7 -0
- package/examples/pocket-tasks/docs/HANDOFF.md +16 -0
- package/examples/pocket-tasks/docs/MISSION.md +15 -0
- package/examples/pocket-tasks/docs/VERIFICATION.md +58 -0
- package/examples/pocket-tasks/package.json +1 -0
- package/examples/pocket-tasks/public/app.js +84 -0
- package/examples/pocket-tasks/public/index.html +12 -0
- package/examples/pocket-tasks/public/styles.css +1 -0
- package/examples/pocket-tasks/server.mjs +79 -0
- package/examples/pocket-tasks/src/domain.mjs +31 -0
- package/examples/pocket-tasks/src/store.mjs +49 -0
- package/examples/pocket-tasks/tests/app.test.mjs +132 -0
- package/examples/pocket-tasks/tests/ui-errors.test.mjs +49 -0
- package/examples/visual-pilot/README.md +11 -0
- package/examples/visual-pilot/app/app.js +10 -0
- package/examples/visual-pilot/app/index.html +1 -0
- package/examples/visual-pilot/app/reference.png +0 -0
- package/examples/visual-pilot/app/style.css +3 -0
- package/examples/visual-pilot/browser-check.cjs +3 -0
- package/examples/visual-pilot/desktop-actual.png +0 -0
- package/examples/visual-pilot/directions-prompt.txt +1 -0
- package/examples/visual-pilot/directions-v1.png +0 -0
- package/examples/visual-pilot/editorial-mockup-prompt.txt +1 -0
- package/examples/visual-pilot/editorial-mockup-v1.png +0 -0
- package/examples/visual-pilot/mismatch-probe.png +0 -0
- package/examples/visual-pilot/mobile-actual.png +0 -0
- package/examples/visual-pilot/quick-filter/AGENT-RESULT.md +25 -0
- package/examples/visual-pilot/quick-filter/baseline.log +42 -0
- package/examples/visual-pilot/quick-filter/filter.mjs +3 -0
- package/examples/visual-pilot/quick-filter/filter.test.mjs +9 -0
- package/package.json +15 -3
- package/scripts/check-codex-git.mjs +20 -0
- package/scripts/check-docs.mjs +14 -0
- package/scripts/check-native-sandbox.mjs +11 -0
- package/scripts/comparison-readiness.mjs +26 -0
- package/scripts/comparison.mjs +42 -0
- package/scripts/evaluation.mjs +64 -0
- package/scripts/fullstack-mission.mjs +30 -0
- package/scripts/hosts/checks.mjs +13 -0
- package/scripts/hosts/codex-meter.mjs +17 -0
- package/scripts/hosts/codex-rpc.mjs +44 -0
- package/scripts/hosts/codex-task.mjs +29 -0
- package/scripts/hosts/codex.mjs +28 -0
- package/scripts/media/demo-actions.cjs +16 -0
- package/scripts/media/encode-demo.py +22 -0
- package/scripts/media/record-demo.cjs +30 -0
- package/scripts/media/visual-short/check.cjs +1 -0
- package/scripts/media/visual-short/encode.py +16 -0
- package/scripts/media/visual-short/record.cjs +4 -0
- package/scripts/media/visual-short/story.py +34 -0
- package/scripts/native-host.mjs +55 -0
- package/scripts/package-smoke.mjs +64 -0
- package/scripts/probe-codex-children.mjs +41 -0
- package/scripts/run-comparison-v2.mjs +45 -0
- package/scripts/run-native-pilot.mjs +76 -0
- package/scripts/summarize-native-pilot.mjs +7 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "devmethod-fullstack-fixture",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=22"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build:api": "tsc -p api/tsconfig.json",
|
|
10
|
+
"build:web": "next build web --webpack",
|
|
11
|
+
"build": "npm run build:api && npm run build:web",
|
|
12
|
+
"test": "npm run build:api && node --test tests/unit.test.cjs tests/http.test.cjs tests/web-model.test.cjs",
|
|
13
|
+
"test:db": "npm run build:api && node --test tests/postgres.test.cjs",
|
|
14
|
+
"db:migrate": "npm run build:api && node build/infrastructure/migrate.js",
|
|
15
|
+
"start:api": "node build/main.js",
|
|
16
|
+
"dev:web": "next dev web -p 3100 --hostname 127.0.0.1",
|
|
17
|
+
"start:web": "next start web -p 3100 --hostname 127.0.0.1",
|
|
18
|
+
"test:e2e": "npm run build && node --test tests/e2e.test.cjs"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@nestjs/common": "12.0.1",
|
|
22
|
+
"@nestjs/core": "12.0.1",
|
|
23
|
+
"@nestjs/platform-express": "12.0.1",
|
|
24
|
+
"drizzle-orm": "0.45.2",
|
|
25
|
+
"next": "16.3.5",
|
|
26
|
+
"pg": "8.23.0",
|
|
27
|
+
"react": "19.3.0",
|
|
28
|
+
"react-dom": "19.3.0",
|
|
29
|
+
"reflect-metadata": "0.2.2",
|
|
30
|
+
"rxjs": "7.8.2",
|
|
31
|
+
"server-only": "0.0.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "5.9.3",
|
|
35
|
+
"@types/node": "22.20.2",
|
|
36
|
+
"@types/react": "19.3.0",
|
|
37
|
+
"@types/react-dom": "19.2.3",
|
|
38
|
+
"@types/pg": "8.16.0"
|
|
39
|
+
},
|
|
40
|
+
"overrides": {
|
|
41
|
+
"@nestjs/platform-express": {
|
|
42
|
+
"multer": "2.3.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
4
|
+
const { once } = require('node:events');
|
|
5
|
+
const { Pool } = require('pg');
|
|
6
|
+
const { createApp } = require('../build/http');
|
|
7
|
+
const { PostgresTasks } = require('../build/infrastructure/postgres');
|
|
8
|
+
const { migrateDatabase } = require('../build/infrastructure/migrate');
|
|
9
|
+
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is required; use an isolated fixture database.');
|
|
10
|
+
test('Next production HTML renders a task created through Nest and PostgreSQL', { timeout: 30000 }, async (t) => {
|
|
11
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
12
|
+
let app;
|
|
13
|
+
let task;
|
|
14
|
+
let web;
|
|
15
|
+
t.after(async () => {
|
|
16
|
+
if (web && web.exitCode === null) { const exited = once(web, 'exit'); web.kill('SIGTERM'); await exited; }
|
|
17
|
+
if (app) await app.close();
|
|
18
|
+
if (task) await pool.query('delete from tasks where id=$1', [task.id]);
|
|
19
|
+
await pool.end();
|
|
20
|
+
});
|
|
21
|
+
await migrateDatabase(pool);
|
|
22
|
+
app = await createApp(new PostgresTasks(pool));
|
|
23
|
+
await app.listen(0, '127.0.0.1');
|
|
24
|
+
const api = await app.getUrl();
|
|
25
|
+
const rejected = await fetch(`${api}/tasks`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ title: 'before\u0000after' }) });
|
|
26
|
+
assert.equal(rejected.status, 400);
|
|
27
|
+
const created = await fetch(`${api}/tasks`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ title: 'Verify the full stack' }) });
|
|
28
|
+
assert.equal(created.status, 201);
|
|
29
|
+
task = await created.json();
|
|
30
|
+
web = spawn(process.execPath, [require.resolve('next/dist/bin/next'), 'start', 'web', '--port', '0', '--hostname', '127.0.0.1'], {
|
|
31
|
+
cwd: require('node:path').resolve(__dirname, '..'),
|
|
32
|
+
env: { ...process.env, TASKS_API_URL: api, NEXT_TELEMETRY_DISABLED: '1' }, stdio: ['ignore', 'pipe', 'pipe'],
|
|
33
|
+
});
|
|
34
|
+
const url = await new Promise((resolve, reject) => {
|
|
35
|
+
let output = '';
|
|
36
|
+
const timer = setTimeout(() => reject(new Error('Next did not become ready.')), 15000);
|
|
37
|
+
web.once('error', (error) => { clearTimeout(timer); reject(error); });
|
|
38
|
+
web.once('exit', (code) => { clearTimeout(timer); reject(new Error(`Next exited before readiness: ${code}`)); });
|
|
39
|
+
const read = (chunk) => {
|
|
40
|
+
output += chunk.toString();
|
|
41
|
+
const match = output.match(/http:\/\/127\.0\.0\.1:\d+/);
|
|
42
|
+
if (match && /Ready in/.test(output)) { clearTimeout(timer); resolve(match[0]); }
|
|
43
|
+
};
|
|
44
|
+
web.stdout.on('data', read); web.stderr.on('data', read);
|
|
45
|
+
});
|
|
46
|
+
const page = await fetch(url);
|
|
47
|
+
assert.equal(page.status, 200);
|
|
48
|
+
const html = await page.text();
|
|
49
|
+
assert.match(html, /Verify the full stack/);
|
|
50
|
+
assert.match(html, /Filter tasks/);
|
|
51
|
+
await app.close();
|
|
52
|
+
const unavailable = await fetch(url);
|
|
53
|
+
assert.match(await unavailable.text(), /Tasks are unavailable/);
|
|
54
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const { createApp } = require('../build/http');
|
|
4
|
+
test('Nest HTTP contract validates input and exposes created tasks', async (t) => {
|
|
5
|
+
const rows = [];
|
|
6
|
+
const app = await createApp({ list: async () => rows, insert: async (row) => { rows.push(row); return row; } });
|
|
7
|
+
await app.listen(0, '127.0.0.1');
|
|
8
|
+
t.after(() => app.close());
|
|
9
|
+
const url = await app.getUrl();
|
|
10
|
+
const post = (body) => fetch(`${url}/tasks`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
|
|
11
|
+
for (const body of [{ title: '' }, { title: 42 }, { title: 'before\u0000after' }, { title: 'x'.repeat(121) }, { title: 'ok', admin: true }, []]) {
|
|
12
|
+
assert.equal((await post(body)).status, 400);
|
|
13
|
+
}
|
|
14
|
+
const multipart = new FormData();
|
|
15
|
+
multipart.set('title', 'Do not accept uploads');
|
|
16
|
+
assert.equal((await fetch(`${url}/tasks`, { method: 'POST', body: multipart })).status, 400);
|
|
17
|
+
assert.equal(rows.length, 0);
|
|
18
|
+
const response = await post({ title: ' Publish evidence ' });
|
|
19
|
+
assert.equal(response.status, 201);
|
|
20
|
+
const task = await response.json();
|
|
21
|
+
assert.equal(task.title, 'Publish evidence');
|
|
22
|
+
const listed = await fetch(`${url}/tasks`);
|
|
23
|
+
assert.equal(listed.status, 200);
|
|
24
|
+
assert.deepEqual(await listed.json(), [task]);
|
|
25
|
+
});
|
|
26
|
+
test('infrastructure failure returns 500 without exposing driver secrets', async (t) => {
|
|
27
|
+
const app = await createApp({ list: async () => { throw new Error('private-db-credential'); }, insert: async () => { throw new Error('private-db-credential'); } });
|
|
28
|
+
await app.listen(0, '127.0.0.1');
|
|
29
|
+
t.after(() => app.close());
|
|
30
|
+
const response = await fetch(`${await app.getUrl()}/tasks`);
|
|
31
|
+
assert.equal(response.status, 500);
|
|
32
|
+
assert.doesNotMatch(await response.text(), /private-db-credential/);
|
|
33
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const { Pool } = require('pg');
|
|
4
|
+
const { randomUUID } = require('node:crypto');
|
|
5
|
+
const { migrateDatabase } = require('../build/infrastructure/migrate');
|
|
6
|
+
const { PostgresTasks } = require('../build/infrastructure/postgres');
|
|
7
|
+
// Refuse to silently pass when a database was not configured.
|
|
8
|
+
if (!process.env.DATABASE_URL) throw new Error('DATABASE_URL is required; use an isolated fixture database.');
|
|
9
|
+
test('real PostgreSQL migration replay, durability and constraints', async (t) => {
|
|
10
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
11
|
+
const ids = [randomUUID(), randomUUID(), randomUUID()];
|
|
12
|
+
t.after(async () => {
|
|
13
|
+
try { await pool.query('delete from tasks where id = ANY($1::uuid[])', [ids]); }
|
|
14
|
+
catch (error) { if (error.code !== '42P01') throw error; }
|
|
15
|
+
finally { await pool.end(); }
|
|
16
|
+
});
|
|
17
|
+
await migrateDatabase(pool);
|
|
18
|
+
await migrateDatabase(pool);
|
|
19
|
+
const task = { id: ids[0], title: 'Verify persistence' };
|
|
20
|
+
await new PostgresTasks(pool).insert(task);
|
|
21
|
+
const secondPool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
22
|
+
try {
|
|
23
|
+
const result = await secondPool.query('select id, title from tasks where id = $1', [task.id]);
|
|
24
|
+
assert.deepEqual(result.rows, [task]);
|
|
25
|
+
} finally { await secondPool.end(); }
|
|
26
|
+
await assert.rejects(pool.query('insert into tasks (id,title) values ($1,$2)', [ids[1], ' ']), { code: '23514' });
|
|
27
|
+
await assert.rejects(pool.query('insert into tasks (id,title) values ($1,$2)', [ids[2], 'x'.repeat(121)]), { code: '22001' });
|
|
28
|
+
await assert.rejects(new PostgresTasks(pool).insert(task), (error) => error.cause?.code === '23505');
|
|
29
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const { taskTitle, InvalidTitle } = require('../build/domain/task');
|
|
4
|
+
const { Tasks } = require('../build/application/tasks');
|
|
5
|
+
test('title normalization and boundaries are authoritative', () => {
|
|
6
|
+
assert.equal(taskTitle(' Review contract '), 'Review contract');
|
|
7
|
+
assert.equal(taskTitle('x'.repeat(120)).length, 120);
|
|
8
|
+
for (const value of [null, 12, '', ' ', 'x'.repeat(121), '\u0000', 'before\u0000after']) assert.throws(() => taskTitle(value), InvalidTitle);
|
|
9
|
+
});
|
|
10
|
+
test('invalid input never reaches persistence and valid creation returns its durable identifier', async () => {
|
|
11
|
+
const writes = [];
|
|
12
|
+
const service = new Tasks({ list: async () => writes, insert: async (task) => { writes.push(task); return task; } });
|
|
13
|
+
assert.throws(() => service.create(' '), InvalidTitle);
|
|
14
|
+
assert.throws(() => service.create('before\u0000after'), InvalidTitle);
|
|
15
|
+
assert.equal(writes.length, 0);
|
|
16
|
+
const created = await service.create(' Read ADR ');
|
|
17
|
+
assert.equal(created.title, 'Read ADR');
|
|
18
|
+
assert.match(created.id, /^[0-9a-f-]{36}$/);
|
|
19
|
+
assert.deepEqual(await service.list(), [created]);
|
|
20
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { test } = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
const { readFileSync } = require('node:fs');
|
|
4
|
+
const { resolve } = require('node:path');
|
|
5
|
+
const ts = require('typescript');
|
|
6
|
+
const moduleObject = { exports: {} };
|
|
7
|
+
const code = ts.transpileModule(readFileSync(resolve(__dirname, '../web/features/tasks/model/tasks.ts'), 'utf8'), {
|
|
8
|
+
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 },
|
|
9
|
+
}).outputText;
|
|
10
|
+
new Function('exports', 'module', code)(moduleObject.exports, moduleObject);
|
|
11
|
+
const { filterTasks, parseTasks } = moduleObject.exports;
|
|
12
|
+
test('filtering is normalized and does not mutate server data', () => {
|
|
13
|
+
const tasks = Object.freeze([Object.freeze({ id: '1', title: 'Review ADR' }), Object.freeze({ id: '2', title: 'Ship fixture' })]);
|
|
14
|
+
assert.deepEqual(filterTasks(tasks, ' REVIEW '), [tasks[0]]);
|
|
15
|
+
assert.deepEqual(filterTasks(tasks, 'missing'), []);
|
|
16
|
+
assert.deepEqual(filterTasks(tasks, ''), tasks);
|
|
17
|
+
});
|
|
18
|
+
test('the server boundary rejects malformed data and strips unknown fields', () => {
|
|
19
|
+
for (const value of [null, {}, [{ id: 12, title: 'wrong' }], new Array(101).fill({ id: '1', title: 'x' })]) {
|
|
20
|
+
assert.throws(() => parseTasks(value), /Unexpected tasks response/);
|
|
21
|
+
}
|
|
22
|
+
assert.deepEqual(parseTasks([{ id: '1', title: 'Read ADR', secret: 'never serialize' }]), [{ id: '1', title: 'Read ADR' }]);
|
|
23
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { listTasks } from '../features/tasks/server/list-tasks';
|
|
2
|
+
import { TaskList } from '../features/tasks/components/task-list';
|
|
3
|
+
export const dynamic = 'force-dynamic';
|
|
4
|
+
export default async function Page() {
|
|
5
|
+
try {
|
|
6
|
+
const tasks = await listTasks();
|
|
7
|
+
return <main><h1>Team tasks</h1><p>Local DevMethod fixture. Showing up to 100 tasks.</p><TaskList tasks={tasks} /></main>;
|
|
8
|
+
} catch {
|
|
9
|
+
return <main><h1>Team tasks</h1><p role="alert">Tasks are unavailable. Start the local API and database, then reload.</p></main>;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Task } from '../model/tasks';
|
|
3
|
+
import { useTaskFilter } from '../hooks/use-task-filter';
|
|
4
|
+
export function TaskList({ tasks }: { tasks: Task[] }) {
|
|
5
|
+
const { query, setQuery, visibleTasks } = useTaskFilter(tasks);
|
|
6
|
+
return <section aria-label="Tasks">
|
|
7
|
+
<label htmlFor="task-filter">Filter tasks</label>{' '}
|
|
8
|
+
<input id="task-filter" value={query} onChange={(event) => setQuery(event.target.value)} />
|
|
9
|
+
{visibleTasks.length ? <ul>{visibleTasks.map((task) => <li key={task.id}>{task.title}</li>)}</ul> : <p>No matching tasks.</p>}
|
|
10
|
+
</section>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { filterTasks, Task } from '../model/tasks';
|
|
4
|
+
export function useTaskFilter(tasks: readonly Task[]) {
|
|
5
|
+
const [query, setQuery] = useState('');
|
|
6
|
+
return { query, setQuery, visibleTasks: filterTasks(tasks, query) };
|
|
7
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface Task { id: string; title: string }
|
|
2
|
+
export function filterTasks(tasks: readonly Task[], query: string): Task[] {
|
|
3
|
+
const search = query.trim().toLowerCase();
|
|
4
|
+
return tasks.filter((task) => task.title.toLowerCase().includes(search));
|
|
5
|
+
}
|
|
6
|
+
export function parseTasks(value: unknown): Task[] {
|
|
7
|
+
if (!Array.isArray(value) || value.length > 100 || value.some((task) =>
|
|
8
|
+
!task || typeof task !== 'object' || typeof task.id !== 'string' || typeof task.title !== 'string')) {
|
|
9
|
+
throw new Error('Unexpected tasks response.');
|
|
10
|
+
}
|
|
11
|
+
return value.map(({ id, title }) => ({ id, title }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import 'server-only';
|
|
2
|
+
import { parseTasks } from '../model/tasks';
|
|
3
|
+
export async function listTasks() {
|
|
4
|
+
const base = process.env.TASKS_API_URL ?? 'http://127.0.0.1:3101';
|
|
5
|
+
const response = await fetch(new URL('/tasks', base), { cache: 'no-store', signal: AbortSignal.timeout(3000) });
|
|
6
|
+
if (!response.ok) throw new Error('Tasks API unavailable.');
|
|
7
|
+
return parseTasks(await response.json());
|
|
8
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": [
|
|
5
|
+
"dom",
|
|
6
|
+
"dom.iterable",
|
|
7
|
+
"esnext"
|
|
8
|
+
],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"module": "esnext",
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"esModuleInterop": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"isolatedModules": true,
|
|
18
|
+
"plugins": [
|
|
19
|
+
{
|
|
20
|
+
"name": "next"
|
|
21
|
+
}
|
|
22
|
+
],
|
|
23
|
+
"allowJs": true,
|
|
24
|
+
"incremental": true
|
|
25
|
+
},
|
|
26
|
+
"include": [
|
|
27
|
+
"next-env.d.ts",
|
|
28
|
+
"**/*.ts",
|
|
29
|
+
"**/*.tsx",
|
|
30
|
+
".next/types/**/*.ts",
|
|
31
|
+
".next/dev/types/**/*.ts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": [
|
|
34
|
+
"node_modules"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"id": "TASK-TITLE",
|
|
4
|
+
"path": "quick",
|
|
5
|
+
"outcome": "Reject empty task titles while preserving valid titles.",
|
|
6
|
+
"scope": ["Task title validation"],
|
|
7
|
+
"exclusions": ["API redesign", "Publication"],
|
|
8
|
+
"invariants": ["Preserve the existing task API"],
|
|
9
|
+
"uncertainties": [],
|
|
10
|
+
"owner": "mission-owner",
|
|
11
|
+
"status": "active",
|
|
12
|
+
"nextAction": "Inspect the title implementation and run its acceptance check.",
|
|
13
|
+
"stopConditions": ["Acceptance verified and diff reviewed", "A required contract is unresolved"],
|
|
14
|
+
"acceptance": [{"id": "AC1", "description": "Whitespace-only titles are rejected", "changes": ["examples/mission/CONTRACT.md"], "verification": "Inspect the fictional title contract; this template does not claim an executed application test.", "kind": "design-review"}],
|
|
15
|
+
"sources": [{"id": "title-contract", "path": "examples/mission/CONTRACT.md", "level": "domain", "reason": "Defines accepted title validation", "authority": "Authority for this fictional exercise's title semantics only", "kind": "accepted-decision", "revision": "working-tree; captured bytes are pinned by context"}],
|
|
16
|
+
"dependencies": [],
|
|
17
|
+
"contradictions": []
|
|
18
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"status": "active",
|
|
4
|
+
"concurrency": 1,
|
|
5
|
+
"contractOwner": "supervisor",
|
|
6
|
+
"sharedContracts": ["contracts"],
|
|
7
|
+
"tasks": [
|
|
8
|
+
{"id": "api", "owner": "api-worker", "worktree": "worktrees/api", "owns": ["src/api"], "dependsOn": [], "status": "pending", "attempts": 0, "maxAttempts": 2, "checkpoint": "checkpoint.json", "evidence": "missing", "acceptance": ["API tests pass"]},
|
|
9
|
+
{"id": "web", "owner": "web-worker", "worktree": "worktrees/web", "owns": ["src/web"], "dependsOn": ["api"], "status": "pending", "attempts": 0, "maxAttempts": 2, "checkpoint": "checkpoint.json", "evidence": "missing", "acceptance": ["Web contract matches API"]}
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Pocket Tasks — greenfield acceptance contract
|
|
2
|
+
|
|
3
|
+
Build a complete local task manager from this empty project. The owner delegates routine implementation decisions within this contract. Use the installed DevMethod skills to frame, plan, implement, verify, review and hand off. Scope is Standard. No deployment, external services, purchases or authentication.
|
|
4
|
+
|
|
5
|
+
A user can add a task, edit its title, complete/reopen it, filter all/active/completed, delete it, and retain tasks across application restart. Interface: responsive light layout, labeled task input, readable list, visible empty/loading/error states, keyboard-operable buttons, clear active filter and status announcements. Render user text safely, including HTML-looking strings.
|
|
6
|
+
|
|
7
|
+
Implementation constraints: Node.js >=22 built-ins, no runtime npm dependencies; ordinary HTML/CSS/browser JavaScript. Keep domain validation, durable storage, HTTP and UI responsibilities separate. Bind only 127.0.0.1. JSON file persistence is sufficient for this single-process fictional application; atomic replacement, no silent data reset if storage is malformed. No databases or cloud providers are needed for this bounded test.
|
|
8
|
+
|
|
9
|
+
Export createTaskServer({dataFile}) from server.mjs, returning an unstarted Node HTTP server. A direct node server.mjs invocation starts on process.env.PORT or 4318, with process.env.DATA_FILE or data/tasks.json. npm start invokes server.mjs; npm test executes meaningful node:test checks authored with the application.
|
|
10
|
+
|
|
11
|
+
HTTP contract:
|
|
12
|
+
- GET /api/tasks -> 200 {tasks:[{id,title,done}]}
|
|
13
|
+
- POST /api/tasks JSON {title} -> 201 {task:{id,title,done:false}}
|
|
14
|
+
- PATCH /api/tasks/:id JSON {title? ,done?} -> 200 {task}; at least one supported field required
|
|
15
|
+
- DELETE /api/tasks/:id -> 204; unknown ID -> 404
|
|
16
|
+
- Trim title, require string length 1..120, reject invalid types; done must be boolean. Invalid input and malformed JSON -> 400, JSON error body. Invalid body container (null, array) -> 400. Request body >16KiB -> 413.
|
|
17
|
+
- Unsupported routes -> 404; never serve files outside the public asset directory.
|
|
18
|
+
|
|
19
|
+
AC1 CRUD and filtering usable in browser. AC2 invalid input rejected without changing state. AC3 persistence survives close/restart. AC4 no HTML injection through titles. AC5 keyboard and narrow viewport usable. AC6 automated tests and README startup/recovery instructions. AC7 DevMethod context, decisions, verification and resumable handoff reflect actual work.
|
|
20
|
+
|
|
21
|
+
Do not edit BRIEF.md or installed method sources. Fill PROJECT_PROFILE.md with actual project context. Create lightweight mission/decision/verification records; no empty template-only claim counts as completion. No Git commits by implementation worker. The owner supplies a separate acceptance harness and browser checks after implementation.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Pocket Tasks — project profile
|
|
2
|
+
|
|
3
|
+
- Objective: complete the fictional local task-manager contract AC1–AC7 in BRIEF.md.
|
|
4
|
+
- Phase and scope: Standard greenfield implementation; no deployment, authentication, external services, purchases or worker commits.
|
|
5
|
+
- Sources and decisions: BRIEF.md and CONTRIBUTING.md supplied by owner, inspected 2026-09-13; docs/ADR-001.md records delegated implementation choice.
|
|
6
|
+
- Readiness: contract complete, no unresolved dependency; pre-implementation assessment retained in docs/MISSION.md.
|
|
7
|
+
- UI direction: light responsive local task workspace; no approved visual artifact supplied. Owner browser acceptance pending.
|
|
8
|
+
- Canonical code: this isolated `greenfield-v1/project` repository/worktree; working tree implementation, uncommitted by instruction.
|
|
9
|
+
- Stack: Node.js >=22 built-ins; npm scripts; HTML/CSS/browser JavaScript; zero dependencies and no lockfile needed.
|
|
10
|
+
- Boundaries: pure validation, serialized file storage, HTTP composition, browser UI. Single process owns the chosen task JSON file.
|
|
11
|
+
- Commands: `npm start`, `npm test`; PORT and DATA_FILE optional for startup.
|
|
12
|
+
- Risks: invalid input must not mutate state; persistence and failure recovery; HTML-looking titles; keyboard/narrow rendering; concurrent writes in one process.
|
|
13
|
+
- Data: fictional user-entered titles stored locally until deletion; no secrets used, retention/backups controlled by local operator.
|
|
14
|
+
- Integrations: loopback HTTP API defined in BRIEF.md; no cloud integration.
|
|
15
|
+
- Rendering: static public assets with interactive client; fixed allowlist, textContent title rendering.
|
|
16
|
+
- Policy and budget: no model child runs, no remote CI, no publication or commits by implementation worker. Loopback execution authorized for checks; no financial spend.
|
|
17
|
+
- Skills loaded: installed `.agents/skills/project-foundation`, `decision-architecture`, `scoped-delivery`; installer provenance and integrity in local DevMethod manifest. Other modules not relevant to this vanilla local app.
|
|
18
|
+
- Assumptions: single-user/process filesystem is sufficient as explicitly permitted; revisit for multiple concurrent server processes.
|
|
19
|
+
- Next independent action: owner acceptance/browser verification of PT-1; no unrelated feature backlog.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Pocket Tasks
|
|
2
|
+
|
|
3
|
+
A fictional, local single-user task manager built from BRIEF.md using installed DevMethod 0.1.0 skills. Add, rename, complete/reopen, filter and delete tasks. Titles are rendered as text, including HTML-looking input.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
Requires Node.js 22 or newer and npm. There are no runtime or development npm dependencies; no install step is needed.
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm start
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Open http://127.0.0.1:4318. The direct startup command always binds to loopback. Stop with Ctrl+C. Optional shell variables:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
PORT=4320 DATA_FILE=/absolute/path/tasks.json npm start
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The default data file is `data/tasks.json` beneath this project. It is created on the first successful mutation. Use one server process per file. No cloud service, account or authentication is involved; anyone with local access to the app can change its tasks. Do not expose it using a proxy or public bind.
|
|
20
|
+
|
|
21
|
+
## Use
|
|
22
|
+
|
|
23
|
+
Type a task and choose Add task. Use its checkbox to complete/reopen, Edit to change its title, Save or Cancel to finish editing (Escape also cancels), and Delete to remove it. All/Active/Completed filters affect the displayed list. Deletion is immediate. The interface supports keyboard navigation and small screens; status and error messages are announced. If a change fails over the network, use Reload tasks to reconcile saved state before retrying; the server may have already saved it.
|
|
24
|
+
|
|
25
|
+
## Test
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
npm test
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Node's test runner exercises domain boundaries, actual loopback HTTP CRUD, invalid and oversized input, restart persistence, concurrent writes, malformed-storage protection and static-file restrictions. HTTP tests need permission to bind ephemeral loopback ports. No remote requests are made. Browser interaction and narrow viewport checks are separate acceptance work; see `docs/VERIFICATION.md` for current evidence.
|
|
32
|
+
|
|
33
|
+
## Data, backups and recovery
|
|
34
|
+
|
|
35
|
+
Tasks persist as a JSON array of `{id,title,done}` records. Mutations serialize in this process, fsync a sibling temporary file, then atomically rename it over the live file. This is not a multi-process database or a universal power-loss durability guarantee.
|
|
36
|
+
|
|
37
|
+
Back up the data file while the app is stopped, and keep backups private. A missing file represents a new empty collection; an unreadable, malformed, or invalid existing file causes a JSON 500 error without silently resetting or replacing it. To recover: stop the server, copy the affected file to a safe backup, inspect it and restore a known-good backup or repair the JSON/schema, ensure the file and parent directory are readable/writable, then restart. Only deliberately replace it with `[]` when loss of all current tasks is intended. Stale `.tmp` siblings from an interrupted write are not loaded; inspect and remove those only while the server is stopped. If the port is occupied, stop the other process or select a different PORT.
|
|
38
|
+
|
|
39
|
+
## Boundaries and API
|
|
40
|
+
|
|
41
|
+
`src/domain.mjs` validates titles and input shapes; `src/store.mjs` serializes persistence; `server.mjs` maps HTTP and explicitly allowlists three static assets; `public/` contains presentation and interaction. Exported `createTaskServer({dataFile})` returns an unstarted server for embedding/tests; callers choose their listener. Direct CLI startup binds 127.0.0.1.
|
|
42
|
+
|
|
43
|
+
The canonical route/status/validation contract remains `BRIEF.md`. GET/POST `/api/tasks`, PATCH/DELETE `/api/tasks/:id`; errors use `{error:string}`. Body limit is 16 KiB. POST/PATCH require `Content-Type: application/json` (optional charset allowed); other media types receive JSON 415. Every request must use Host `127.0.0.1:<actual server port>`; other hosts receive JSON 403. Mutations reject external or null Origin and `Sec-Fetch-Site: cross-site` with JSON 403. Same-origin browser requests and CLI requests without Origin remain supported. These browser-boundary checks do not authenticate local clients. Unsupported routes/methods return 404. No authentication, deployment, external integration, telemetry or dependency updates are included.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# ADR-001 — Local serialized JSON persistence
|
|
2
|
+
|
|
3
|
+
Date: 2026-09-13. Status: implementation decision under owner delegation in BRIEF.md.
|
|
4
|
+
|
|
5
|
+
Node built-ins and single-process JSON persistence are contract constraints. Domain validation is pure, a storage adapter serializes read-modify-write transactions, HTTP maps statuses, and browser code owns interaction only. Atomic sibling temporary file replacement prevents partially written live JSON. Each mutation rereads current data and validates persisted records; malformed data fails closed without overwriting it. A failed transaction does not poison subsequent operations. fsync the temporary file before rename; no claim of power-loss durability on every filesystem. No multi-process writer support.
|
|
6
|
+
|
|
7
|
+
Alternative in-memory cache with delayed writes risks loss on restart; database would violate the bounded dependency scope. Revisit the storage choice for multi-user or multi-process operation. Local files contain user-entered titles, retained until deletion; backups remain the operator’s responsibility. API and mutation ambiguity: clients do not blindly retry writes after network failures, since operation may already have persisted.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# PT-1 — resumable handoff
|
|
2
|
+
|
|
3
|
+
Archived worker record at handoff. Subsequent coordinator acceptance, browser checks and final review are recorded in [the consolidated validation](../../../docs/GREENFIELD-VALIDATION.md). “Outstanding” below describes the worker handoff, not current release status.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Done: local app implemented; nine automated tests pass with loopback permission. Method sources and BRIEF were preserved. No commit, publication, purchase, deployment or extra worker was performed. Source hashes, actual runtime and limitations are in VERIFICATION.md; baseline commit is `0cf3474bfd777299873e51a2789829692c648130`.
|
|
7
|
+
|
|
8
|
+
Dirty state: PROJECT_PROFILE.md updated; README.md, package.json, server.mjs, src/, public/, tests/ and docs/ created. Owner may be running a separate browser instance with its own data file; coordinate before restarting or changing these frozen source files. Worker never read or edited the sibling independent verification directory.
|
|
9
|
+
|
|
10
|
+
Outstanding: owner independent API/browser acceptance including real keyboard, filtering, injection and narrow viewport. Independent review found missing local HTTP browser boundaries; worker corrected Host/Origin/MIME checks and added a non-mutation regression test. Evidence is in VERIFICATION.md. The coordinator owns any resulting method guidance evolution. This single fictional project cannot establish superiority over another method, universal reliability, or production readiness.
|
|
11
|
+
|
|
12
|
+
Resumption: inspect actual source/hash and owner acceptance results; fix specific findings within BRIEF if any, invalidate/re-run affected evidence, then record acceptance. Work beyond this app or integration remains owned by the coordinating agent. Single-process file storage and lack of auth/deployment are explicit scope boundaries.
|
|
13
|
+
|
|
14
|
+
Next command: `$project-foundation verify PT-1`.
|
|
15
|
+
|
|
16
|
+
Latest correction: owner browser observation of misleading HTTP 400 feedback resolved. Client preserves input and permits immediate correction for 4xx; network/5xx retain reconciliation. Two VM-based UI regression tests added; browser confirmation belongs to the owner. Source app.js hash updated in VERIFICATION.md.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# PT-1 — Pocket Tasks
|
|
2
|
+
|
|
3
|
+
## Foundation / frame / design / architecture / plan / ready — recorded before implementation
|
|
4
|
+
|
|
5
|
+
Inputs: BRIEF.md, CONTRIBUTING.md, installed project-foundation, decision-architecture and scoped-delivery skills and their work-sizing, operating commands, backend boundaries, HTTP contracts, verification references. Standard scope is explicit in BRIEF.
|
|
6
|
+
|
|
7
|
+
Outcome: a local single-user task manager implementing AC1–AC7. Exclusions: authentication, deployment, external services, multi-process storage, arbitrary files. The owner has delegated routine choices within the contract.
|
|
8
|
+
|
|
9
|
+
Design: light responsive centered workspace, add form, three pressed-state filter buttons, task checkboxes and explicit Edit/Delete actions, inline edit form, live status and alert errors. No external fonts or assets. No approved image reference exists; this direction is an implementation choice within the brief.
|
|
10
|
+
|
|
11
|
+
Plan: (1) validate task invariants and serialize atomic file transactions; (2) map HTTP contract and safe static allowlist; (3) implement accessible UI; (4) run domain/HTTP/persistence/failure tests and self-review; (5) hand off for independent acceptance and browser checks.
|
|
12
|
+
|
|
13
|
+
Ready assessment: contract, constraints, permission and test strategy are sufficient. No unresolved dependency. ADR-001 records persistence boundary. Done requires real automated evidence and owner browser acceptance; implementation worker cannot claim independent acceptance.
|
|
14
|
+
|
|
15
|
+
Next command at readiness: `$project-foundation implement PT-1`.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# PT-1 — implementation verification
|
|
2
|
+
|
|
3
|
+
Archived worker record at handoff. Subsequent coordinator acceptance, browser checks and final review are recorded in [the consolidated validation](../../../docs/GREENFIELD-VALIDATION.md). “Outstanding” below describes the worker handoff, not current release status.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
2026-09-13. Worker-authored checks; not an independent review. Inspected baseline commit `0cf3474bfd777299873e51a2789829692c648130` plus uncommitted files below. Runtime actually used: Node v23.10.0 on macOS; Node 22 was specified as minimum but not separately executed here.
|
|
7
|
+
|
|
8
|
+
## Execution and correction
|
|
9
|
+
|
|
10
|
+
The pre-code readiness/design/plan outputs were recorded in MISSION.md before source creation. Implementation followed the domain/store/HTTP/UI slice. Self-review found that invalid persisted titles could leak the domain's HTTP 400 error classification; changed stored-record validation to throw a storage error (500) and added an empty persisted-title fixture. No contract weakening.
|
|
11
|
+
|
|
12
|
+
First `npm test`: 1 domain test passed, 5 HTTP tests could not bind because the outer sandbox denied loopback (EPERM). Second run with authorized loopback: 6/6 passed. After the stored-title correction, final `npm test` with loopback permission: 6/6 passed, 0 skipped/failed, 300 ms reported duration. No tests were disabled. The SHA utility failed due to host locale configuration; Node crypto computed the hashes below instead.
|
|
13
|
+
|
|
14
|
+
## Acceptance evidence
|
|
15
|
+
|
|
16
|
+
| Criterion | Worker evidence | Remaining acceptance |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| AC1 CRUD/filtering | HTTP CRUD/reopen/delete tests pass; client filter implementation inspected | Owner real browser actions and filters |
|
|
19
|
+
| AC2 invalid input | Bad types, null/array, empty/overlong title, empty patch, malformed JSON, >16 KiB rejected; stored bytes unchanged | Independent harness |
|
|
20
|
+
| AC3 restart persistence | Close/recreate server sees saved record; deletion persists; 20 concurrent creates retained | Independent restart check |
|
|
21
|
+
| AC4 safe title rendering | HTML-looking string round-trip; title uses textContent, no innerHTML; CSP | Owner real browser injection check |
|
|
22
|
+
| AC5 keyboard/narrow | Semantic forms/buttons/labels, checkbox labels, pressed filters, live status, responsive CSS inspected | Owner keyboard/narrow browser check |
|
|
23
|
+
| AC6 tests/recovery | Six Node tests executed; README commands and explicit backup/repair instructions | Owner startup acceptance |
|
|
24
|
+
| AC7 context/handoff | Filled profile, mission readiness, ADR, this evidence and HANDOFF | Owner method-path assessment |
|
|
25
|
+
|
|
26
|
+
The reviewer is the implementation worker here; no independent review is claimed. Native browser evidence belongs to the owner and is not reconstructed into worker evidence. No deployment or production use was tested.
|
|
27
|
+
|
|
28
|
+
## Evidence identity (SHA-256)
|
|
29
|
+
|
|
30
|
+
- BRIEF.md: `574ff435d8ac66f39cad76d0318c6476afea061737b0c217599c0e401c541125`
|
|
31
|
+
- server.mjs: `9791de6e7c63790ce837ed9ddd80f6f50edfc3465bc553621aaf8e529cd19ecd`
|
|
32
|
+
- src/domain.mjs: `0a65f1775d4de877985730a344857e84d359d4a9259c29cd43e5e3c991b606db`
|
|
33
|
+
- src/store.mjs: `abf87b665bbe34dca33dff02acdca4389d2a32063dfb55d61cbe9b6b6ae36208`
|
|
34
|
+
- public/index.html: `420e4d1cf29535298196a5da2ee43603662c7189908be9fed01a1e068d4316e9`
|
|
35
|
+
- public/styles.css: `a3868709460fb1cf522a56a65e8bcf132074c2fb8ed65ac5384d134bd5cde251`
|
|
36
|
+
- public/app.js: `38b2ad64ac707c94856f0808db11e96f0fb86f71a8bb888c2a97f1b82423eef0`
|
|
37
|
+
- tests/app.test.mjs: `c08c030b2232e2f6139f10db505e8fc089102a2cf7156a35c937cfa27aba7703`
|
|
38
|
+
- package.json: `92dd7058064ae4b18c0cfcabcb7585c526e50bf1bccd5e3e706cbf302878f1e3`
|
|
39
|
+
|
|
40
|
+
## Independent review correction — local browser boundary
|
|
41
|
+
|
|
42
|
+
The coordinator's independent review identified a real omission: loopback binding alone did not stop cross-origin simple requests or untrusted Host values. The original version accepted JSON-shaped bodies with text/plain and had no Host/Origin validation. This finding invalidates the original HTTP-boundary sufficiency claim; it does not change BRIEF or installed skills.
|
|
43
|
+
|
|
44
|
+
Correction: require the exact `127.0.0.1:<effective local port>` Host, reject duplicate Host; reject external/null Origin, duplicate Origin or Sec-Fetch-Site cross-site on POST/PATCH/DELETE; require application/json for POST/PATCH (415), preserve same-origin and Origin-free CLI clients. No authentication guarantee is claimed.
|
|
45
|
+
|
|
46
|
+
Added real HTTP test verifies 24 hostile Origin/Host mutation combinations, eight unsupported-MIME writes, a hostile read, unchanged persisted bytes, a valid same-origin update with charset, and an Origin-free CLI update. Its first run failed because the raw test client omitted Content-Length for DELETE/GET bodies (socket hang-up); corrected the test transport framing, without weakening assertions. Final `npm test`: **7/7 pass**, 0 failed/skipped, 1221 ms reported duration. Current source hashes above reflect this correction. UI sources remain unchanged. Owner independent harness/browser rerun remains separate.
|
|
47
|
+
|
|
48
|
+
Next command: `$project-foundation verify PT-1` for independent owner acceptance.
|
|
49
|
+
|
|
50
|
+
## Browser-found validation feedback correction
|
|
51
|
+
|
|
52
|
+
The owner browser check found that a whitespace title received HTTP 400 but the UI described it as an ambiguous save and requested reload. Updated the client to distinguish definitive 4xx rejection from network/5xx ambiguity. Validation now preserves add/edit input, restores an unsuccessfully toggled checkbox, focuses the editable input and permits immediate correction without reload. Network/5xx failures still offer reconciliation.
|
|
53
|
+
|
|
54
|
+
Added two tests executing the real client script in Node VM with a minimal DOM substitute: validation input preservation/immediate second submission, and network/server reconciliation messaging. These check control flow, not rendered browser accessibility. Final npm test: **9/9 pass**, 0 failed/skipped, 340 ms. Owner browser recheck remains separate.
|
|
55
|
+
|
|
56
|
+
- tests/ui-errors.test.mjs SHA-256: cb70e0628dfa78c33bd02b6d799bf533dfb0217cc009760d4f5ec24d63cdc70d
|
|
57
|
+
|
|
58
|
+
Next command: `$project-foundation verify PT-1` (owner browser check).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"name":"pocket-tasks-greenfield","version":"1.0.0","private":true,"type":"module","engines":{"node":">=22"},"scripts":{"start":"node server.mjs","test":"node --test"}}
|