changelog: 'by' attribution + Web Designer self-logs via the skill
- migration 012: cja_changelog.actor column - seed-changelog: optional 5th 'by' element (defaults to Carlos Arias); display shows 'Added · time · by <actor>' - changelog skill documents the actor field + attribution logic (agents name themselves, human/CLI edits are Carlos Arias) - Web Designer prompt now logs each change to the changelog via the skill, attributed to 'Website Designer Agent'; runner reseeds cja_changelog when the seed file changed (the agent has no shell)
This commit is contained in:
parent
2ad551dc5f
commit
df5e5f9456
7 changed files with 62 additions and 4 deletions
|
|
@ -18,10 +18,12 @@ Editing the file alone changes nothing live — steps 2 and 3 are required.
|
|||
|
||||
## Entry format
|
||||
|
||||
Each entry is a 4-element array: `[type, summary, detail, 'YYYY-MM-DD HH:MM']`
|
||||
Each entry is an array of `[type, summary, detail, 'YYYY-MM-DD HH:MM', by?]` — the 5th
|
||||
element (**by**, the actor) is optional:
|
||||
|
||||
```php
|
||||
['added', 'Contact form wired up', 'Real submissions stored in the database, with validation and spam protection.', '2026-07-23 12:14'],
|
||||
['updated', 'Hero spacing tightened', 'Trimmed the vertical rhythm on the homepage hero.', '2026-07-24 09:00', 'Website Designer Agent'],
|
||||
```
|
||||
|
||||
- **type** — one of: `added`, `updated`, `fixed`, `bug`, `removed`, `note`.
|
||||
|
|
@ -33,6 +35,14 @@ Each entry is a 4-element array: `[type, summary, detail, 'YYYY-MM-DD HH:MM']`
|
|||
orders the timeline** — the page shows entries newest-first by this value, not by array
|
||||
position — so a new entry can go anywhere in the array as long as its timestamp is right.
|
||||
Use the real date/time the change happened; if unknown, use now.
|
||||
- **by** (optional, 5th element) — **who made the change.** The changelog shows it as
|
||||
"Added · 09:00 · by …". Attribute it to whoever actually did the work:
|
||||
- An **agent** attributes the entry to **itself** — the Web Designer agent uses
|
||||
`'Website Designer Agent'`.
|
||||
- A change made by the site owner (e.g. directed through the Claude CLI) is
|
||||
`'Carlos Arias'`, which is also the **default** when the 5th element is omitted.
|
||||
- Only name an agent when that agent genuinely made the change. If the Web Designer didn't
|
||||
do it, it's `'Carlos Arias'`.
|
||||
|
||||
## Voice
|
||||
|
||||
|
|
|
|||
|
|
@ -435,6 +435,12 @@ function liveUrl(target) {
|
|||
return target;
|
||||
}
|
||||
|
||||
function nowStamp() {
|
||||
const d = new Date();
|
||||
const p = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
function buildDesignPrompt(task) {
|
||||
let assets = { images: [], videos: [] };
|
||||
try { assets = JSON.parse(task.assets || "{}") || {}; } catch {}
|
||||
|
|
@ -484,7 +490,17 @@ function buildDesignPrompt(task) {
|
|||
for (const v of videos) p.push(`- ${v}`);
|
||||
p.push("");
|
||||
}
|
||||
p.push("Keep the change scoped to what's asked and leave the working tree with only your intended edits.");
|
||||
p.push(
|
||||
"",
|
||||
"After you finish the change, log it to the public changelog using your `changelog` skill:",
|
||||
"- Add ONE entry to the $entries array in api/cli/seed-changelog.php, in the site's visitor-facing voice.",
|
||||
`- Use the timestamp '${nowStamp()}' and attribute it to 'Website Designer Agent' (the 5th array element).`,
|
||||
"- Choose the right type (added / updated / fixed / removed).",
|
||||
"- Do NOT run the reseed or the build — the console does that for you.",
|
||||
"- If you ended up making no change to the site, do not add a changelog entry.",
|
||||
"",
|
||||
"Keep the change scoped to what's asked and leave the working tree with only your intended edits.",
|
||||
);
|
||||
return p.join("\n");
|
||||
}
|
||||
|
||||
|
|
@ -529,6 +545,13 @@ async function runDesignTask(task) {
|
|||
return resolve({ ok: true, commit: "", summary: summary || "No changes were needed.", live: liveUrl(task.target_page) });
|
||||
}
|
||||
|
||||
// The agent logs to the changelog by editing api/cli/seed-changelog.php
|
||||
// (it has no shell). If it did, reseed cja_changelog so the build picks up
|
||||
// the new entry — the changelog page reads the DB at build time.
|
||||
if (/seed-changelog\.php/.test(status.tail)) {
|
||||
await phpCli(["api/cli/seed-changelog.php"]);
|
||||
}
|
||||
|
||||
// Build must pass before anything is committed.
|
||||
const built = await build({});
|
||||
if (!built.ok) {
|
||||
|
|
|
|||
|
|
@ -53,12 +53,17 @@ $entries = [
|
|||
|
||||
Db::execute('TRUNCATE TABLE cja_changelog');
|
||||
|
||||
// Each entry is [type, summary, detail, 'YYYY-MM-DD HH:MM', by?]. The 5th
|
||||
// element (who made the change) is optional and defaults to the site owner.
|
||||
$n = 0;
|
||||
foreach ($entries as [$type, $summary, $detail, $at]) {
|
||||
foreach ($entries as $e) {
|
||||
[$type, $summary, $detail, $at] = $e;
|
||||
$by = $e[4] ?? 'Carlos Arias';
|
||||
Db::insert('cja_changelog', [
|
||||
'type' => $type,
|
||||
'summary' => $summary,
|
||||
'detail' => $detail,
|
||||
'actor' => $by,
|
||||
'entry_at' => $at . ':00',
|
||||
'published' => 1,
|
||||
]);
|
||||
|
|
|
|||
7
api/db/migrations/012_changelog_actor.sql
Normal file
7
api/db/migrations/012_changelog_actor.sql
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-- Attribution for changelog entries: who made the change.
|
||||
--
|
||||
-- e.g. "Added · 00:40 · by Website Designer Agent". Blank = unattributed (the
|
||||
-- display simply omits the "by …" clause). Seeded from api/cli/seed-changelog.php.
|
||||
|
||||
ALTER TABLE `cja_changelog`
|
||||
ADD COLUMN `actor` varchar(80) NOT NULL DEFAULT '' AFTER `detail`;
|
||||
|
|
@ -28,7 +28,7 @@ export async function getChangelog() {
|
|||
try {
|
||||
conn = await mysql.createConnection({ host, user, password, database });
|
||||
const [rows] = await conn.execute(
|
||||
`SELECT type, summary, detail,
|
||||
`SELECT type, summary, detail, actor,
|
||||
DATE_FORMAT(entry_at, '%Y-%m-%dT%H:%i') AS entry_at
|
||||
FROM cja_changelog
|
||||
WHERE published = 1
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ const jsonLd = {
|
|||
<time class="ca-log-time" datetime={e.entry_at}>
|
||||
{time(e.entry_at)}
|
||||
</time>
|
||||
{e.actor && <span class="ca-log-by">by {e.actor}</span>}
|
||||
</div>
|
||||
<p class="ca-log-summary">{e.summary}</p>
|
||||
{e.detail && <p class="ca-log-detail">{e.detail}</p>}
|
||||
|
|
|
|||
|
|
@ -1008,6 +1008,18 @@
|
|||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.ca-log-by {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.62rem;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--ca-ink-4);
|
||||
}
|
||||
.ca-log-by::before {
|
||||
content: "·";
|
||||
margin-right: 0.5rem;
|
||||
color: var(--ca-rule);
|
||||
}
|
||||
|
||||
.ca-log-summary {
|
||||
margin: 0;
|
||||
font-size: 0.95rem;
|
||||
|
|
|
|||
Loading…
Reference in a new issue