From aca76692430210ef3cf7f3bfcfd4383c0b00b3d4 Mon Sep 17 00:00:00 2001 From: "Claude (Chronicler #78)" Date: Sat, 11 Apr 2026 14:38:24 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Add=20spec=5Fpath=20field=20=E2=80=94?= =?UTF-8?q?=20link=20tasks=20to=20git=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - spec_path column added to tasks table - API POST/PATCH support spec_path field - Discord /tasks detail view shows '📄 Full Spec' link to Gitea when spec exists - Backfilled 7 existing tasks with spec directory paths Architecture: Database tracks state, Git stores documentation, they link to each other. Chronicler #78 | firefrost-services --- services/arbiter-3.0/src/discord/tasks.js | 5 +++++ services/arbiter-3.0/src/routes/api.js | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/services/arbiter-3.0/src/discord/tasks.js b/services/arbiter-3.0/src/discord/tasks.js index eb66e98..d299904 100644 --- a/services/arbiter-3.0/src/discord/tasks.js +++ b/services/arbiter-3.0/src/discord/tasks.js @@ -166,6 +166,11 @@ async function handleTaskDetail(interaction, taskNumber) { embed.addFields({ name: 'Tags', value: t.tags.map(tag => `\`${tag}\``).join(' '), inline: false }); } + if (t.spec_path) { + const specUrl = `https://git.firefrostgaming.com/firefrost-gaming/firefrost-operations-manual/src/branch/master/${t.spec_path}`; + embed.addFields({ name: '📄 Full Spec', value: `[View in Gitea](${specUrl})`, inline: false }); + } + if (t.completed_at) { const completedDate = new Date(t.completed_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); embed.addFields({ name: 'Completed', value: `${completedDate}${t.completed_by ? ` by ${t.completed_by}` : ''}`, inline: false }); diff --git a/services/arbiter-3.0/src/routes/api.js b/services/arbiter-3.0/src/routes/api.js index e397e17..e7bd061 100644 --- a/services/arbiter-3.0/src/routes/api.js +++ b/services/arbiter-3.0/src/routes/api.js @@ -454,7 +454,7 @@ router.get('/tasks/summary', async (req, res) => { // POST /api/internal/tasks — Create a new task router.post('/tasks', async (req, res) => { try { - const { title, description, priority, owner, tags } = req.body; + const { title, description, priority, owner, tags, spec_path } = req.body; if (!title) return res.status(400).json({ error: 'Title required' }); // Auto-assign next task number @@ -462,9 +462,9 @@ router.post('/tasks', async (req, res) => { const taskNumber = maxResult.rows[0].next; const result = await db.query( - `INSERT INTO tasks (task_number, title, description, priority, owner, tags) - VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`, - [taskNumber, title, description || null, priority || 'medium', owner || 'unassigned', tags || null] + `INSERT INTO tasks (task_number, title, description, priority, owner, tags, spec_path) + VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, + [taskNumber, title, description || null, priority || 'medium', owner || 'unassigned', tags || null, spec_path || null] ); console.log(`📋 [Tasks] Created #${taskNumber}: ${title}`); res.json({ success: true, task: result.rows[0] }); @@ -478,7 +478,7 @@ router.post('/tasks', async (req, res) => { router.patch('/tasks/:id', async (req, res) => { try { const { id } = req.params; - const { status, priority, owner, title, description, completed_by } = req.body; + const { status, priority, owner, title, description, completed_by, spec_path } = req.body; const updates = []; const params = []; @@ -489,6 +489,7 @@ router.patch('/tasks/:id', async (req, res) => { if (owner) { p++; updates.push(`owner = $${p}`); params.push(owner); } if (title) { p++; updates.push(`title = $${p}`); params.push(title); } if (description !== undefined) { p++; updates.push(`description = $${p}`); params.push(description); } + if (spec_path !== undefined) { p++; updates.push(`spec_path = $${p}`); params.push(spec_path); } // Auto-set completed_at when marking done if (status === 'done') {