docs(audit): align documentation with post-remediation backend reality

- Update data model enums to match backend models
- Update API reference auth requirements
- Add dispute module references and warning blocks
- Add 2026-05-24 audit remediation callout to Overview
- Generate task breakdowns and audit artifacts
- Add doc alignment report (.taskmaster/reports/)
This commit is contained in:
Siavash Sameni
2026-05-24 11:16:29 +04:00
parent b824ca0435
commit 4cf5c49274
74 changed files with 5964 additions and 81 deletions

View File

@@ -0,0 +1,217 @@
#!/usr/bin/env node
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const taskmasterPath = path.join(repoRoot, ".taskmaster", "tasks", "tasks.json");
const outputDir = path.join(repoRoot, "Taskmaster");
const taskDir = path.join(outputDir, "Tasks");
const STATUS_CHECKBOX = {
done: "x",
completed: "x",
};
const PRIORITY_TAG = {
high: "#priority/high",
medium: "#priority/medium",
low: "#priority/low",
};
const PRIORITY_EMOJI = {
highest: "🔺",
high: "⏫",
medium: "🔼",
low: "🔽",
lowest: "⏬",
};
const source = JSON.parse(await readFile(taskmasterPath, "utf8"));
const tasks = source.master?.tasks ?? source.tasks ?? [];
const generatedAt = new Date().toISOString();
await rm(outputDir, { recursive: true, force: true });
await mkdir(taskDir, { recursive: true });
const flatTasks = tasks.flatMap((task) => {
const parent = normalizeTask(task);
const subtasks = (task.subtasks ?? []).map((subtask) =>
normalizeTask({
...subtask,
id: `${task.id}.${subtask.id}`,
parentId: task.id,
parentTitle: task.title,
priority: subtask.priority ?? task.priority,
}),
);
return [parent, ...subtasks];
});
for (const task of flatTasks) {
await writeFile(path.join(taskDir, `${slugTaskId(task.id)}.md`), renderTaskNote(task));
}
await writeFile(path.join(outputDir, "README.md"), renderDashboard(flatTasks));
await writeFile(path.join(outputDir, "tasks.md"), renderTasksFile(flatTasks));
console.log(`Exported ${flatTasks.length} Taskmaster tasks to ${path.relative(repoRoot, outputDir)}`);
function normalizeTask(task) {
return {
id: String(task.id),
title: task.title ?? "Untitled task",
description: task.description ?? "",
details: task.details ?? "",
testStrategy: task.testStrategy ?? "",
status: task.status ?? "pending",
priority: task.priority ?? "medium",
dependencies: (task.dependencies ?? []).map(String),
parentId: task.parentId && task.parentId !== "undefined" ? String(task.parentId) : "",
parentTitle: task.parentTitle ?? "",
};
}
function renderTaskNote(task) {
return `---
taskmaster_id: "${yamlEscape(task.id)}"
status: "${yamlEscape(task.status)}"
priority: "${yamlEscape(task.priority)}"
depends_on: [${task.dependencies.map((id) => `"${yamlEscape(id)}"`).join(", ")}]
parent_id: "${yamlEscape(task.parentId)}"
source: "taskmaster"
generated_at: "${generatedAt}"
---
# ${task.id} - ${task.title}
${renderObsidianTaskLine(task)}
## Metadata
| Field | Value |
| --- | --- |
| Taskmaster ID | ${task.id} |
| Status | ${task.status} |
| Priority | ${task.priority} |
| Dependencies | ${task.dependencies.length ? task.dependencies.join(", ") : "None"} |
| Parent | ${task.parentId ? `${task.parentId} - ${task.parentTitle}` : "None"} |
## Description
${task.description || "_No description._"}
## Details
${task.details || "_No details._"}
## Verification
${task.testStrategy || "_No verification strategy._"}
`;
}
function renderDashboard(tasks) {
const rows = tasks
.map((task) => `| [[Tasks/${slugTaskId(task.id)}|${task.id}]] | ${escapeTable(task.title)} | ${task.status} | ${task.priority} | ${task.dependencies.length ? task.dependencies.join(", ") : "None"} |`)
.join("\n");
return `# Taskmaster Dashboard
Generated from \`.taskmaster/tasks/tasks.json\` at ${generatedAt}.
Taskmaster remains the canonical source of truth. Re-run:
\`\`\`sh
node scripts/export-taskmaster-to-obsidian.mjs
\`\`\`
## Status Summary
${renderStatusSummary(tasks)}
## Task Index
| ID | Title | Status | Priority | Dependencies |
| --- | --- | --- | --- | --- |
${rows}
## Obsidian Tasks Query
\`\`\`tasks
not done
tag includes #taskmaster
sort by priority
sort by description
\`\`\`
`;
}
function renderTasksFile(tasks) {
const lines = tasks.map(renderObsidianTaskLine).join("\n");
return `# Taskmaster Tasks
Generated from \`.taskmaster/tasks/tasks.json\` at ${generatedAt}.
These lines use the Obsidian Tasks emoji format:
- standard Markdown checkbox syntax
- \`#taskmaster\` tag for filtering
- priority emoji where available
- \`🆔\` task IDs
- \`\` dependency IDs
${lines}
`;
}
function renderObsidianTaskLine(task) {
const checkbox = STATUS_CHECKBOX[task.status] ?? " ";
const priorityTag = PRIORITY_TAG[task.priority] ?? "#priority/none";
const priorityEmoji = PRIORITY_EMOJI[task.priority] ?? "";
const dependencyMarkers = task.dependencies.map((id) => `⛔ tm-${safeId(id)}`).join(" ");
const fields = [
"#taskmaster",
priorityTag,
`#status/${tagValue(task.status)}`,
priorityEmoji,
`🆔 tm-${safeId(task.id)}`,
dependencyMarkers,
].filter(Boolean);
return `- [${checkbox}] ${task.id} - ${task.title} ${fields.join(" ")}`;
}
function renderStatusSummary(tasks) {
const counts = tasks.reduce((acc, task) => {
acc[task.status] = (acc[task.status] ?? 0) + 1;
return acc;
}, {});
return Object.entries(counts)
.sort(([a], [b]) => a.localeCompare(b))
.map(([status, count]) => `- ${status}: ${count}`)
.join("\n");
}
function slugTaskId(id) {
return `task-${safeId(id)}`;
}
function safeId(id) {
return String(id).replace(/[^A-Za-z0-9_-]+/g, "-");
}
function tagValue(value) {
return String(value || "none").toLowerCase().replace(/[^a-z0-9_-]+/g, "-");
}
function yamlEscape(value) {
return String(value ?? "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
}
function escapeTable(value) {
return String(value ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ");
}