@antoneeo/mkt-agentic-sdlc-skill 0.26.0 → 0.27.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/CHANGELOG.md +12 -0
- package/gemini-extension.json +1 -1
- package/package.json +1 -1
- package/skills/mkt-agentic-sdlc/ENFORCEMENT.md +3 -3
- package/skills/mkt-agentic-sdlc/SKILL.md +1 -1
- package/skills/mkt-agentic-sdlc/docs_site/app.js +7 -1
- package/skills/mkt-agentic-sdlc/docs_site/strings.js +9 -0
- package/skills/mkt-agentic-sdlc/docs_site/style.css +2 -0
- package/skills/mkt-agentic-sdlc/guides.md +8 -0
- package/skills/mkt-agentic-sdlc/review.md +9 -1
- package/skills/mkt-agentic-sdlc/scripts/docs_site.py +17 -3
- package/skills/mkt-agentic-sdlc/scripts/knowledge.py +925 -33
- package/skills/mkt-agentic-sdlc/scripts/mkt_check.py +2 -1
- package/skills/mkt-agentic-sdlc/scripts/sdlc_core.py +562 -22
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.27.0] - 2026-10-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Shared knowledge layer (F-084, kb CHANGELOG): `inventory <path>` and orient's upper bound of never-examined project files reach this lens; `validate`/`check` refuse a malformed `audit/INVENTORY_exclusions.md` row and the new `DECLARED`/`DIVERGES` claim rules; the docs site renders them. `sdlc_core.skip_dir` is extracted from `iter_files`, behaviour unchanged.
|
|
7
|
+
- `constraints --unit <id>` and `constraints --file <path>` (shared core): the constraints an ANALYSIS states in extractable form, each with its line, ending with the sections they could not extract.
|
|
8
|
+
- `benefit` and the docs site recognize a REVIEW_LOG row with tier `post-merge` (written by the code lens: the owner's report of a wrong behaviour in a merged change): `benefit` keeps it out of every review figure, the docs site shows it as "defect reported after merge". `review.md`: a reviewer derives callers and behaviours before comparing with the constraint view.
|
|
9
|
+
- `guides.md`: a code guide may carry a `## Preservation constraints` section copied from a closed unit; it survives regeneration. Nothing else changes in this lens: `validate` adds no note.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Shared claim checks (`check`): a `RULING` row on a note that names a foreign origin (`imported_from:`) is refused — relabelling an imported row is not ratifying it; the message for a row superseded by an `IMPORTED` one names the ratification act (F-083).
|
|
13
|
+
- Guide-verdict check (`verdict --hook`, shared core): a level quoted inside a sentence (`Level: L1` in prose) no longer counts as a declaration: only a line that opens with the level word does, the rule the constraint recall already uses (level 1 alone, `L1` or `E1`; levels 2 and 3 with `router` on the same line). The check now judges the project, and its docs-root name, that holds the edited file, not the one around the session's working directory; after a `cd` out of the project it let every edit through. A router verdict follows the same rule: only a line that opens with `router` or declares the level with it counts, never `router: no match` quoted in a sentence; `Level: Spike · router: …` and markdown openers (`##`, `1.`, `•`) count. A quote that itself opens a line (blockquote, code fence) still counts. The protected-path gate (`gate --hook`, `gate --file`) also judges the edited file's project, falls back to the session's project when no docs root sits above the file, and stays silent instead of exiting with an error when two docs roots sit side by side. Its `--protected` prefixes are relative to the nearest docs root above the edited file: with nested docs roots (a monorepo package with its own `ai_docs/`, or a docs folder inside a protected path) rewrite them relative to that root, or the path is no longer protected.
|
|
14
|
+
|
|
3
15
|
## [0.26.0] - 2026-10-23
|
|
4
16
|
|
|
5
17
|
### Changed
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mkt-agentic-sdlc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Evidence-First marketing planning protocol with engagement triage, SOSTAC workflow, evidence ledger and optional devPNT integration.",
|
|
5
5
|
"author": "Antonio Pinto (https://github.com/Antoneeo)"
|
|
6
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antoneeo/mkt-agentic-sdlc-skill",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Evidence-First marketing planning protocol for Claude Code, Gemini CLI, Google Antigravity and Codex: engagement triage, SOSTAC workflow, evidence ledger, mechanical validator and optional devPNT integration.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -54,15 +54,15 @@ Semantics: exit code 2 + message on stderr ⇒ the write is blocked and the mess
|
|
|
54
54
|
|
|
55
55
|
**Usage warnings:**
|
|
56
56
|
- The gate is deliberately coarse: applied to all of `src/` it would also block the legitimate L1/L2 tasks foreseen by the Triage. Use it **only on security-critical directories** (`--protected "src/auth;src/crypto"`), where "never without analysis" is the desired policy.
|
|
57
|
-
- The paths in `--protected` are prefixes relative to the project
|
|
57
|
+
- The paths in `--protected` are prefixes relative to the root of the project the hook judges (below), separated by `;`. With nested docs roots (a monorepo whose package has its own), that is the nearest one above the edited file: write the prefixes relative to it.
|
|
58
58
|
- `ai_docs/`, `tests/` and `test/` are always excluded from blocking.
|
|
59
|
-
- The hook
|
|
59
|
+
- The hook judges the project, and the docs root, holding the edited file: the session's working directory can move with a `cd`, so it is never the reference. With no docs root above the file, it is the session's own project, as before. `--root <path>` overrides it; `gate --file` resolves the same way, and two docs roots side by side leave the gate silent.
|
|
60
60
|
- **Hybrid/devPNT projects**: add `--hybrid` to the gate command. Governed designs live in the devPNT DB, so the gate also unlocks when an approved E-TDD shadow (`ai_docs/solutions/SHADOW_*tdd*.md`, exported before implementation — see `hybrid.md` §Ownership matrix) is present. Without the flag the gate would block legitimate governed work. The flag is deliberately explicit: never auto-detected.
|
|
61
61
|
|
|
62
62
|
### Verdict check: `verdict` (PreToolUse -- manual for this lens)
|
|
63
63
|
|
|
64
64
|
`mkt_check.py verdict --hook` refuses an Edit, Write, MultiEdit or NotebookEdit when the
|
|
65
|
-
session transcript shows no guide-router verdict (`router: …`, or a level-1 declaration)
|
|
65
|
+
session transcript shows no guide-router verdict (a line that opens with `router: …` or with the level, `Level: L2 · router: …`, `Level: Spike · router: …`; or one that opens with a level-1 declaration)
|
|
66
66
|
or a guide the verdict names was never read; it is silent in projects without guides and
|
|
67
67
|
whenever it cannot tell. The code package (`agentic-sdlc`) wires it machine-wide on
|
|
68
68
|
Claude Code; this package does not. To wire it by hand, add a `PreToolUse` entry with
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mkt-agentic-sdlc
|
|
3
|
-
version: 0.
|
|
3
|
+
version: 0.27.0
|
|
4
4
|
description: Evidence-First marketing planning protocol with engagement triage, SOSTAC workflow, evidence ledger, adversarial CMO review, a complete Standalone mode and optional symbiosis with devPNT. Use for marketing plans, go-to-market strategy, campaign planning and market research.
|
|
5
5
|
author: Antonio Pinto (https://github.com/Antoneeo)
|
|
6
6
|
copyright: (c) 2026 Antonio Pinto
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
var D = null; // documents, loaded on first use
|
|
13
13
|
var K = null; // knowledge base, loaded on first use
|
|
14
14
|
var app = document.getElementById("app");
|
|
15
|
-
var ORIGINS = ["given", "elicited", "derived", "ruling", "imported"];
|
|
15
|
+
var ORIGINS = ["given", "elicited", "derived", "ruling", "imported", "declared"];
|
|
16
16
|
|
|
17
17
|
// ------------------------------------------------------------- helpers
|
|
18
18
|
|
|
@@ -483,6 +483,7 @@
|
|
|
483
483
|
if (f.statement) marks.push(el("span", { "class": "tag statement", title: t("statement_notice"), text: t("statement_mark") }));
|
|
484
484
|
if (f.state === "superseded") marks.push(el("span", { "class": "tag replaced", title: t("state_superseded_why"), text: t("state_superseded") }));
|
|
485
485
|
if (f.state === "contested") marks.push(el("span", { "class": "tag conflict", title: t("state_contested_why"), text: t("state_contested") }));
|
|
486
|
+
if (f.state === "diverges") marks.push(el("span", { "class": "tag conflict", title: t("state_diverges_why"), text: t("state_diverges") }));
|
|
486
487
|
if (opts && opts.topic) marks.push(link("#/topic/" + encodeURIComponent(f.topic), f.topic, "chip"));
|
|
487
488
|
return el("li", { "class": "fact" + (f.state === "superseded" ? " replaced" : ""), onclick: function () { location.hash = "#/fact/" + encodeURIComponent(id); } },
|
|
488
489
|
el("span", { "class": "dot bg-" + oc }),
|
|
@@ -597,6 +598,11 @@
|
|
|
597
598
|
el("p", { "class": "why-line", text: t("state_contested_why") }),
|
|
598
599
|
el("ul", { "class": "facts" }, (f.with || []).map(function (s) { return factLine(s, { topic: true }); })));
|
|
599
600
|
}
|
|
601
|
+
if (f.state === "diverges") {
|
|
602
|
+
parts.push(el("h3", null, el("span", { "class": "tag conflict", text: t("state_diverges") }), " ", t("fact_diverges_from")),
|
|
603
|
+
el("p", { "class": "why-line", text: t("state_diverges_why") }),
|
|
604
|
+
el("ul", { "class": "facts" }, (f.diverges_from || []).map(function (s) { return factLine(s, { topic: true }); })));
|
|
605
|
+
}
|
|
600
606
|
if (f.grounds) {
|
|
601
607
|
parts.push(el("h3", { text: t("fact_rests_on") }),
|
|
602
608
|
el("p", { "class": "why-line", text: t("ground_class_" + f.ground_class) }),
|
|
@@ -56,7 +56,9 @@ window.DOCS_SITE_STRINGS = {
|
|
|
56
56
|
event_request: "request record",
|
|
57
57
|
event_note: "note",
|
|
58
58
|
event_source: "source document received",
|
|
59
|
+
event_sent: "document sent",
|
|
59
60
|
event_review: "review",
|
|
61
|
+
event_defect: "defect reported after merge",
|
|
60
62
|
event_decision: "decision record",
|
|
61
63
|
event_project_note: "project note",
|
|
62
64
|
event_spike: "exploration outcome",
|
|
@@ -231,6 +233,8 @@ window.DOCS_SITE_STRINGS = {
|
|
|
231
233
|
origin_ruling_why: "Taken to settle a conflict.",
|
|
232
234
|
origin_imported: "from another knowledge base",
|
|
233
235
|
origin_imported_why: "Brought in from another knowledge base, where its own sources are.",
|
|
236
|
+
origin_declared: "declared by this project",
|
|
237
|
+
origin_declared_why: "What this project told a recipient, on the day it was sent: a statement of ours, not a fact about the product.",
|
|
234
238
|
origin_other: "origin as written: {v}",
|
|
235
239
|
origin_other_why: "An origin this site does not know.",
|
|
236
240
|
|
|
@@ -239,6 +243,8 @@ window.DOCS_SITE_STRINGS = {
|
|
|
239
243
|
state_superseded_why: "A newer fact takes its place.",
|
|
240
244
|
state_contested: "in conflict",
|
|
241
245
|
state_contested_why: "Facts that disagree, not yet settled.",
|
|
246
|
+
state_diverges: "diverges",
|
|
247
|
+
state_diverges_why: "What we declared contradicts what the project decided. It is corrected by a new send, or the decision changes.",
|
|
242
248
|
|
|
243
249
|
statement_mark: "statement",
|
|
244
250
|
statement_prefix: "[statement] {text}",
|
|
@@ -254,6 +260,7 @@ window.DOCS_SITE_STRINGS = {
|
|
|
254
260
|
fact_replaced_by: "Replaced by",
|
|
255
261
|
fact_current_at_end: "Current fact at the end of each chain",
|
|
256
262
|
fact_in_conflict_with: "In conflict with",
|
|
263
|
+
fact_diverges_from: "Diverges from",
|
|
257
264
|
fact_rests_on: "What it rests on",
|
|
258
265
|
fact_untraced: "This part could not be traced.",
|
|
259
266
|
fact_sources: "Sources",
|
|
@@ -266,6 +273,7 @@ window.DOCS_SITE_STRINGS = {
|
|
|
266
273
|
fact_unknown: "This fact is not in this knowledge base.",
|
|
267
274
|
|
|
268
275
|
ground_document: "a document",
|
|
276
|
+
ground_declared: "a document this project sent",
|
|
269
277
|
ground_statement: "a person's statement",
|
|
270
278
|
ground_decision: "a decision",
|
|
271
279
|
ground_kb: "another knowledge base",
|
|
@@ -273,6 +281,7 @@ window.DOCS_SITE_STRINGS = {
|
|
|
273
281
|
ground_class_statement: "Rests, at least in part, on a person's statement.",
|
|
274
282
|
ground_class_untraceable: "Part of what it rests on could not be traced.",
|
|
275
283
|
ground_class_imported: "Rests on another knowledge base.",
|
|
284
|
+
ground_class_declared: "Rests on our own declaration to a recipient, not on evidence.",
|
|
276
285
|
ground_class_documents: "Rests on documents or decisions only.",
|
|
277
286
|
|
|
278
287
|
chain_loop: "The chain of replacements loops back to {id}: it stops here.",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
--o-derived: #a99be0;
|
|
19
19
|
--o-ruling: #7fa7e6;
|
|
20
20
|
--o-imported: #9cc96b;
|
|
21
|
+
--o-declared: #d98fb0;
|
|
21
22
|
--conflict: #e0605a;
|
|
22
23
|
--display: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua", Charter, Georgia, serif;
|
|
23
24
|
--body: "Segoe UI Variable Text", "Avenir Next", Avenir, "Gill Sans Nova", "Gill Sans", "Trebuchet MS", sans-serif;
|
|
@@ -179,6 +180,7 @@ a.factid code { color: var(--link); }
|
|
|
179
180
|
.o-derived { color: var(--o-derived); } .bg-derived { background: var(--o-derived); }
|
|
180
181
|
.o-ruling { color: var(--o-ruling); } .bg-ruling { background: var(--o-ruling); }
|
|
181
182
|
.o-imported { color: var(--o-imported); } .bg-imported { background: var(--o-imported); }
|
|
183
|
+
.o-declared { color: var(--o-declared); } .bg-declared { background: var(--o-declared); }
|
|
182
184
|
.o-other { color: var(--paper-3); } .bg-other { background: var(--paper-3); }
|
|
183
185
|
|
|
184
186
|
.panel {
|
|
@@ -262,6 +262,12 @@ in the snapshot, never to your assumption about what the code does.
|
|
|
262
262
|
(source-anchored), not an elaborated version of it.
|
|
263
263
|
- When in doubt about whether something is "supported" by the source, treat it
|
|
264
264
|
as not covered rather than stretching the marker to fit.
|
|
265
|
+
- **Declared exception: `## Preservation constraints`.** A code guide may carry
|
|
266
|
+
this section, copied at an L3 closure from the ANALYSIS's table (code lens,
|
|
267
|
+
`SKILL.md` Phase 5). The section's marker cites each row it holds as
|
|
268
|
+
`[source: solutions/ANALYSIS_<name>.md#P<n>]` instead of a snapshot anchor: each
|
|
269
|
+
row records what the code must keep doing, as that unit enumerated and probed it,
|
|
270
|
+
not an excerpt of the code.
|
|
265
271
|
|
|
266
272
|
## 4. Ingestion bound (T9)
|
|
267
273
|
|
|
@@ -293,5 +299,7 @@ state it explicitly when handing off a newly created guide.
|
|
|
293
299
|
closure (docs travel with the code); when you merely READ one, check it against
|
|
294
300
|
the code before trusting it. Too large to absorb → mark it `SUPERSEDED` rather
|
|
295
301
|
than leave a confident-wrong map badged CURRENT, which is worse than none.
|
|
302
|
+
- **A regenerated guide keeps `## Preservation constraints` unchanged**: the
|
|
303
|
+
section does not come from the snapshot (§3), so regeneration carries it over.
|
|
296
304
|
- **Agent-global KB guides** use the same pipeline and validator via
|
|
297
305
|
`--root ~/.agentic-sdlc`; freshness via the same `stale` engine.
|
|
@@ -187,7 +187,10 @@ can block forever gets removed. **Log one row per completed review, PASS or FAIL
|
|
|
187
187
|
logging only passes would erase exactly that evidence. The row goes in
|
|
188
188
|
`ai_docs/audit/reviews/REVIEW_LOG.md` (create it if absent — `templates.md`):
|
|
189
189
|
`| date | doc_key | tier | model | reviewer | findings_raised | findings_real | verdict | revise_rounds |`,
|
|
190
|
-
with `tier` = `design` or `closure` in Standalone.
|
|
190
|
+
with `tier` = `design` or `closure` in Standalone. In the code lens, a wrong behaviour
|
|
191
|
+
the owner reports in a merged L3 change is one row too, tier `post-merge`, `model`
|
|
192
|
+
`none (owner report)`, reviewer `owner`, the owner quoted in the notes: a defect
|
|
193
|
+
record, not a review, so `benefit` keeps it out of every review figure. **`model` records the capability
|
|
191
194
|
tier that actually ran** — `deep`, `light`, `economy`, `single (client exposes no
|
|
192
195
|
choice)`, `below floor: <reason>`, or `none (not due)` on a NOT DUE row — never a provider name in doctrine (a project
|
|
193
196
|
may of course write its own model names in its own log). Without it the floor above
|
|
@@ -285,6 +288,11 @@ The binding inputs, by review type:
|
|
|
285
288
|
clause reads both), and the **probe harness** (`ai_docs/solutions/harness_[feature]/`).
|
|
286
289
|
Hand these *in addition to* the design artifact — the reviewer checks the artifact
|
|
287
290
|
**against** them, not only for internal consistency.
|
|
291
|
+
- **The constraint view comes last.** Where the ANALYSIS carries ids, the reviewer
|
|
292
|
+
derives the callers and behaviours first, then compares with
|
|
293
|
+
`sdlc_check.py constraints --unit <id>`: a constraint the design states and the
|
|
294
|
+
diff breaks is a finding. The view never replaces the ANALYSIS or the diff in the
|
|
295
|
+
request.
|
|
288
296
|
- **For a closure review, the diff and the test evidence**: the run output that
|
|
289
297
|
backs every completion claim, dated after the final relevant change.
|
|
290
298
|
- **For a design or closure review of an L3/E3 unit, the request record**
|
|
@@ -615,6 +615,11 @@ class Grounds:
|
|
|
615
615
|
return [{"kind": "untraceable", "path": rel}]
|
|
616
616
|
seen = seen | {rel}
|
|
617
617
|
if rel.startswith("corpus/given/"):
|
|
618
|
+
# F-084: a document this project SENT is our own declaration, not
|
|
619
|
+
# ground for a fact about the product.
|
|
620
|
+
side = self._meta(rel + ".meta.md")
|
|
621
|
+
if _v(side, "provenance").upper() == "DECLARED":
|
|
622
|
+
return [{"kind": "declared", "path": rel}]
|
|
618
623
|
return [{"kind": "document", "path": rel}]
|
|
619
624
|
if not _inside(self.docs, rel) or not (self.docs / rel).is_file():
|
|
620
625
|
return [{"kind": "untraceable", "path": rel}]
|
|
@@ -643,6 +648,8 @@ def ground_class(leaves):
|
|
|
643
648
|
return "statement"
|
|
644
649
|
if "untraceable" in kinds:
|
|
645
650
|
return "untraceable"
|
|
651
|
+
if "declared" in kinds:
|
|
652
|
+
return "declared"
|
|
646
653
|
if "kb" in kinds:
|
|
647
654
|
return "imported"
|
|
648
655
|
return "documents"
|
|
@@ -707,12 +714,15 @@ def build_kb(docs):
|
|
|
707
714
|
kind, rest = _kind(r)
|
|
708
715
|
f = {"text": r["claim"], "valid": r["valid"], "qty": r["qty"], "about": r["about"],
|
|
709
716
|
"origin": (r["prov"] or "").strip().lower(), "topic": slug,
|
|
710
|
-
"state": {"SUPERSEDED": "superseded", "CONTESTED": "contested"
|
|
717
|
+
"state": {"SUPERSEDED": "superseded", "CONTESTED": "contested",
|
|
718
|
+
"DIVERGES": "diverges"}.get(kind, "ok")}
|
|
711
719
|
if kind == "SUPERSEDED":
|
|
712
720
|
f["successors"] = _ids(rest)
|
|
713
721
|
f["chain_ends"] = chain_ends(r["id"], by_id)
|
|
714
722
|
if kind == "CONTESTED":
|
|
715
723
|
f["with"] = _ids(rest)
|
|
724
|
+
if kind == "DIVERGES":
|
|
725
|
+
f["diverges_from"] = _ids(rest)
|
|
716
726
|
srcs = []
|
|
717
727
|
for src in r["source"].split(";"):
|
|
718
728
|
src = src.strip()
|
|
@@ -1246,7 +1256,9 @@ def chronology(docs, pages):
|
|
|
1246
1256
|
text = _text(docs / rel) if (docs / rel).is_file() else None
|
|
1247
1257
|
if text is not None:
|
|
1248
1258
|
for row in sdlc_core.parse_review_log(text)[0]:
|
|
1249
|
-
|
|
1259
|
+
# F-085: a `post-merge` row is the owner's defect report, not a review
|
|
1260
|
+
kind = "defect" if row["tier"].lower().startswith("post-merge") else "review"
|
|
1261
|
+
add(row["date"], kind, "%s — %s: %s" % (
|
|
1250
1262
|
row["doc_key"], row["tier"], row["verdict"]), rel)
|
|
1251
1263
|
for p in sorted(docs.glob("architecture/ADR_*.md")):
|
|
1252
1264
|
rel = "architecture/" + p.name
|
|
@@ -1269,7 +1281,9 @@ def chronology(docs, pages):
|
|
|
1269
1281
|
continue
|
|
1270
1282
|
if kind == "source":
|
|
1271
1283
|
name = p.name[:-len(suffix)]
|
|
1272
|
-
|
|
1284
|
+
# F-084: a DECLARED sidecar's date is the day it was SENT
|
|
1285
|
+
sent = _v(fm, "provenance").upper() == "DECLARED"
|
|
1286
|
+
add(date, "sent" if sent else kind, name, rel, source=name)
|
|
1273
1287
|
else:
|
|
1274
1288
|
add(date, kind, title_of(rel, text), rel)
|
|
1275
1289
|
# Most recent first. Within one day a document's later entry is the more recent
|