Tuesday

A Simple Agentic Coding Workflow for Java

AI coding agents can already write classes, add tests, refactor code, and fix bugs.

But using an AI agent for coding is not the same as having an agentic development workflow.

If one agent receives a large task, loads the entire repository into context, and does everything by itself, the workflow is still mostly:

prompt
  ↓
generate code
  ↓
hope it is correct

A more useful approach is controlled delegation:

user intent
    ↓
targeted discovery
    ↓
small task contract
    ↓
required planning agents
    ↓
bounded implementation
    ↓
independent review
    ↓
deterministic verification

This is the approach I have been experimenting with in ModelMatrix4J , a Java project I also use to explore agent-first development.

The current workflow looks roughly like this:

              ┌─ Architect
User Intent ──┤
              └─ Test Planner
                    ↓
               Orchestrator
                    ↓
                Implementer
                    ↓
          ┌─ Reviewer
          └─ Adversarial Reviewer
                    ↓
             mvnw verify

The important point is not the number of agents.

The important point is that each agent gets a specific responsibility, a bounded authority, and only the context needed for its part of the task.


What Does Agent-First Mean?

Agent-first means the repository is prepared for coding agents to work inside it without requiring a large custom prompt every time.

A fresh coding agent should be able to determine:

What is the requested outcome?

What am I allowed to change?

What must I not change?

Which repository rules apply?

Which agents are actually needed?

What evidence proves the task is complete?

If these answers exist only inside a developer's head, every new agent session needs the developer to reconstruct the workflow manually.

A better approach is to keep stable boundaries inside the repository while letting the orchestrator discover task-specific details from the codebase.

The user should provide intent and authority.

The user should not need to decide:

which files to edit
which module is affected
which agent should run
whether agents should run in parallel
which tests should be written
which order the work should happen in

Those are orchestration decisions.


Discover Before Loading Context

One lesson from agentic coding is that more context is not always better.

Giving every agent the complete product specification, architecture document, roadmap, full repository history, and every workflow file can make an agent slower and less focused.

The workflow in ModelMatrix4J now starts with read-only discovery.

user intent
    ↓
search affected code
    ↓
find affected modules and symbols
    ↓
find existing tests
    ↓
read nearest AGENTS.md
    ↓
read only relevant doc sections
    ↓
build task contract

The goal is to identify the smallest authorized impact surface before anyone starts writing.

For a small bug fix, an agent may only need:

objective
affected class
existing tests
nearest AGENTS.md
relevant contract
writable scope

It should not automatically load every repository document.

This gives a useful principle:

small context
+
strong boundaries
>
large context
+
weak boundaries

Separate Agents for Separate Jobs

ModelMatrix4J currently uses five canonical engineering roles:

architect
test-planner
implementer
reviewer
adversarial-reviewer

Their repository-level semantics live under:

.agents/roles/

The Codex-specific runtime adapters live separately under:

.codex/agents/

This separation is intentional.

The role file describes what the agent means inside the repository. The Codex adapter describes how that role is executed.

Architect

The architect is read-only.

It is used when the task affects things such as:

public or shared APIs
module boundaries
dependency direction
package boundaries
ownership boundaries
integration seams
framework containment
concurrency ownership

The architect does not need to run for every local implementation change.

Test Planner

The test planner is also read-only.

It is useful when the task changes observable behavior or contains regression-sensitive semantics such as:

timeouts
cancellation
retry behavior
ordering
concurrency
failure handling
negative paths

Implementer

The implementer is the agent that receives writable scope and performs the actual code changes.

It receives only the contract subset and repository context needed for that implementation.

Reviewer

The reviewer independently checks correctness, architecture, scope, public API impact, dependency direction, tests, and failure semantics.

Adversarial Reviewer

The adversarial reviewer intentionally looks for different classes of problems:

race conditions
lost interrupts
resource leaks
deadlocks
hidden retries
incorrect timeout behavior
nondeterminism
scope bypasses
provider leakage
accidental API expansion
false completion claims

Both review agents stay read-only and independent from the implementation agent.


Permissions Are Part of the Design

Agent responsibilities are more useful when they are also reflected in execution permissions.

A simplified architect configuration looks like:

name = "architect"
sandbox_mode = "read-only"
approval_policy = "never"

The implementer is different:

name = "implementer"
sandbox_mode = "workspace-write"

This creates a real distinction between:

thinking about a change
and
being allowed to make the change

Planning and review do not automatically imply write permission.

The role is therefore not only a persona inside a prompt. It is part of the execution boundary.


Keep Repository Instructions Small

Agent instructions can easily turn into another documentation system.

That should be avoided.

The root AGENTS.md should contain only repository-wide rules that most agents need.

For example:

Respect the current product authority.

Change only the delegated writable scope.

Do not silently change shared APIs or dependencies.

Do not implement future functionality early.

Read the nearest module-level AGENTS.md.

Keep agent context minimal.

Use the canonical verification command.

More specific rules should live closer to the module that owns them.

For example:

modelmatrix-core/AGENTS.md
modelmatrix-junit/AGENTS.md

This keeps the root file small and lets agents load instructions incrementally.

The same idea applies to other documentation.

An agent should read a specific architecture or product section because the task requires it, not because every agent always reads every document.


Use Skills for Repeated Workflows

Roles answer:

Who should do this?

Skills answer:

How should this workflow run?

ModelMatrix4J keeps a small set of workflow skills:

.agents/skills/
  modelmatrix-task/
  modelmatrix-review/
  modelmatrix-verify/

The main orchestration entry point is:

modelmatrix-task

It replaced an earlier milestone-specific workflow because repository work is broader than roadmap milestones.

A task may be:

a feature
a bug fix
a refactor
a hardening change
a maintenance task
a roadmap milestone

The main task skill intentionally stays focused on orchestration:

  1. Discover the smallest relevant context.
  2. Create a bounded task contract.
  3. Select only the agents required by the task.
  4. Schedule work according to dependencies.
  5. Delegate writable work to implementers.
  6. Invoke the review workflow.
  7. Invoke final verification.

Detailed review rules live in modelmatrix-review.

Detailed verification rules live in modelmatrix-verify.

This matters because a good agent workflow should also be lazy-loaded.

task orchestration
    ↓
load review protocol when review starts
    ↓
load verification protocol when verification starts

There is no reason to carry every workflow rule through every agent context.


The Task Contract Is the Authority Boundary

Before writable work starts, the orchestrator creates a small canonical task contract.

It contains the important execution boundaries:

authorization
objective
baseline / state identity
writable scope
relevant read-only context
forbidden changes
prerequisites
acceptance evidence
verification requirements
escalation conditions

This is different from asking the user to specify implementation topology.

The orchestrator discovers the likely files and symbols first, then creates the smallest writable scope that can satisfy the requested outcome.

For example, instead of asking:

Which files should I change?

the coding agent should investigate:

Where is this behavior implemented?

Which tests already describe it?

Which module owns it?

Does the solution require a public API change?

Then it delegates the implementation with a bounded contract.

A child agent may receive a smaller subset of the parent contract, but it cannot silently weaken the parent task.


Delegation Should Be Small and Specific

A coding agent should not simply receive:

Implement the next milestone.

A better delegation is closer to:

Objective:
Fix transient retry behavior.

Writable scope:
RetryPolicy.java
RetryPolicyTest.java

Relevant context:
existing retry implementation
specific failure contract

Forbidden:
public API changes
new dependencies
unrelated provider behavior

Evidence:
transient failures retry
permanent failures fail immediately
regression tests pass

The agent knows:

what outcome is required
where it may write
what evidence is required
where its authority stops

That is more useful than giving the agent a very large prompt.


Schedule by Dependency, Not by Role

Having several agents does not mean running all of them sequentially.

The orchestrator owns a dependency graph.

Independent read-only planning can run in parallel:

architect ──────┐
                ├─ parallel
test-planner ───┘
        ↓
     reconcile

Independent reviewers of the same final state can also run in parallel:

reviewer ─────────────┐
                      ├─ parallel
adversarial-reviewer ─┘

Writable work needs stronger constraints.

parallel writers are allowed only when:

prerequisites are satisfied
+
writable scopes do not overlap
+
neither task depends on the other's output
+
each writer has a separate Git worktree

If one change depends on another, the work is sequential.

shared contract
      ↓
implementation using contract
      ↓
review
      ↓
verification

Parallelism is an optimization.

It is not an authority boundary.


Independent Review Comes Before Done

The implementation agent does not decide by itself that the task is complete.

After implementation, the same exact integrated state is sent to two independent read-only reviewers.

The normal reviewer focuses on:

correctness
design clarity
scope
public API
dependencies
tests
failure semantics
concurrency semantics

The adversarial reviewer asks different questions:

What happens on failure?

What assumption is not tested?

Could this leak a resource?

Could a concurrency edge case break it?

Can the implementation escape its writable scope?

Did a framework leak into a provider-neutral module?

Are we calling this complete only because the happy path passes?

Review verdicts are tied to the exact state that was reviewed.

If a reviewer finds a major issue and the implementer changes the code, the previous verdict becomes stale.

implementation
    ↓
review
    ↓
correction
    ↓
fresh review

This sounds obvious, but making it explicit prevents an old approval from being treated as evidence for new code.


Verification Is Different From Review

Review answers:

Does the change look correct?

Verification answers:

Does the final integrated repository actually satisfy the required checks?

For ModelMatrix4J, the canonical build command is:

./mvnw -B verify

The final verification also checks the integrated diff and writable scope.

A green Maven build is important, but it is not automatically evidence for every requirement.

For example:

build passes

does not necessarily prove:

retry semantics are correct
a regression is covered
an architecture boundary was preserved
no unauthorized file was changed

The workflow therefore tracks acceptance evidence separately from the build result.


Keep Sources of Truth Separate

One common problem in AI-assisted repositories is semantic duplication.

The same rule appears in several prompts and configuration files and eventually they disagree.

ModelMatrix4J now separates these responsibilities:

.agents/roles/
    → canonical agent responsibilities

.codex/agents/
    → Codex execution adapters and capabilities

.agents/skills/
    → reusable orchestration workflows

AGENTS.md
    → repository-wide engineering rules

module AGENTS.md
    → local module rules

docs/PRODUCT_SPEC.md
    → product behavior

docs/ARCHITECTURE.md
    → architecture and dependency boundaries

docs/ROADMAP.md
    → product sequencing and milestone authority

The Codex adapter does not redefine what an architect or reviewer means.

It points to the canonical repository role and configures execution details such as read-only or workspace-write access.

This gives one semantic authority per concern without requiring one giant document.


Agent Output Should Also Stay Small

Context optimization is not only about what an agent reads.

It is also about what an agent sends back.

A child agent should not repeat the entire task contract, product specification, architecture document, or diff in its response.

It should return the delta:

decisions
findings
changed files
evidence
blockers
escalations
residual risks

This becomes increasingly important when the orchestrator coordinates several agents.

Without compact outputs, the parent agent's context slowly fills with repeated information.


Not Every Task Needs Every Agent

Agentic development is not about maximizing the number of agents.

A small behavior-preserving local refactor may only need:

discovery
    ↓
implementer
    ↓
review
    ↓
verify

A public API or concurrency change may need:

architect + test-planner
          ↓
      implementer
          ↓
dual independent review
          ↓
       verify

The orchestrator selects agents based on risk and dependency.

The user does not need to know which combination applies.


A Small Agentic Setup Is Enough

You do not need ten agents, a custom Java workflow engine, or a large collection of AI-specific documents.

A useful setup can start with:

Orchestrator
    ↓
Implementer
    ↓
Reviewer
    ↓
Build + Tests

Then add specialized planning or adversarial review only when they solve a real problem.

The useful properties are:

  • high-level user intent
  • read-only discovery before writes
  • small task-specific context
  • clear agent responsibilities
  • bounded writable scopes
  • dependency-aware scheduling
  • independent review
  • state-bound evidence
  • deterministic verification
  • minimal semantic duplication

The Main Idea

The goal of agentic coding is not to run as many agents as possible.

The goal is controlled delegation with the smallest sufficient context.

A useful coding agent should be able to receive a high-level task such as:

Fix retry behavior.
Do not break the public API.
Add the required regression tests.

Then the repository and orchestrator should provide the rest:

discover
   ↓
scope
   ↓
plan when necessary
   ↓
delegate
   ↓
implement
   ↓
review
   ↓
correct
   ↓
re-review
   ↓
verify

The developer provides intent and makes decisions when real product or authority questions appear.

The coding system handles implementation topology.

For me, that is the important shift from AI-assisted coding toward agent-first development:

the right agent
+
the right authority
+
the smallest useful context
+
independent evidence

That is the direction I am currently using in ModelMatrix4J .

Sunday

Specs as Boundaries: A Lightweight SDD Workflow for AI Coding Agents

AI coding agents are no longer just autocomplete tools. They can create project structures, implement features, write tests, refactor code, and explain their decisions.

That is exactly why they need better boundaries.

When an agent is weak, the problem is getting it to produce useful code. When an agent is strong, the problem becomes controlling what it changes, what it ignores, and how it stays aligned with the product.

If we only prompt the agent directly, the project can drift.

A single prompt may be clear in your head, but the agent does not always know the product boundary, the non-goals, the previous decisions, or the intended lifecycle of a feature. Over time, this creates a familiar mess:

“Why did the agent implement this extra thing?”
“Why did it change unrelated code?”
“Where is the actual product behavior documented?”
“Which spec is current and which one was just a temporary plan?”
“Why is the README saying one thing and the code doing another?”

This is where Spec-Driven Development, or SDD, becomes useful.

In this post, I want to explain SDD from a practical point of view: not as theory, not as a strict enterprise process, and not as a replacement for engineering judgment. I see it as a lightweight workflow for building software with AI coding agents.

I will use the workflow I used in my own Java CLI project, AgentTokenInsight, as the example.


I do not see SDD as a replacement for coding, testing, or engineering judgment.

I also do not see it as a heavy enterprise process that every tiny change must follow.

For me, lightweight SDD is useful when an AI coding agent is powerful enough to change a lot of code quickly. The goal is not to slow the agent down. The goal is to give it a clear boundary.

Use SDD for product behavior, APIs, CLI behavior, libraries, compatibility-sensitive changes, and multi-step features.

Do not force it for typos, tiny refactors, throwaway scripts, or experiments.

The point is not documentation. The point is controlled delegation.


What is Spec-Driven Development?

Spec-Driven Development means we do not jump directly from idea to implementation.

Instead, every meaningful change goes through a small lifecycle:

propose → review → apply → test → sync → archive

In plain English:

  1. First, define what should change.
  2. Clarify why it should change.
  3. Write the expected behavior.
  4. List tasks.
  5. Review the scope before implementation.
  6. Implement only that approved scope.
  7. Run tests.
  8. Update the current product spec if behavior changed.
  9. Archive the completed change.

The propose step can create several files, such as:

proposal.md
design.md
tasks.md
spec.md

This is not the same as writing huge requirements documents. In fact, it should be the opposite.

A good SDD workflow should be small, practical, and close to the code.


Why SDD Matters More with AI Coding Agents

Traditional development already benefits from clear specs. But with AI agents, the value becomes much higher.

Why?

Because AI agents are powerful, but they do not always know the real boundary of a task.

Imagine you have a large Java backend project and you ask the agent:

Fix the login timeout bug.

To solve that bug, the agent may only need to inspect a few files:

src/main/java/com/example/auth/LoginService.java
src/main/java/com/example/auth/SessionManager.java
src/test/java/com/example/auth/LoginServiceTest.java

But if the task is not scoped, the agent may start exploring much more:

- all authentication classes
- security configuration
- application.yml
- web.xml
- all session-related tests
- unrelated user-management code
- generated files
- build output
- old archived code

At that point, the agent is not necessarily “wrong”. It is trying to understand the system. But the context has become too large, the token cost has increased, and the change is now harder to review.

The original task was:

Fix the login timeout bug.

But the actual work may turn into:

Analyze half of the authentication module, read several config files, update tests, and maybe touch unrelated code.

This is the kind of drift SDD helps prevent.

Before implementation, the spec can define the boundary clearly:

Scope:
- investigate login timeout behavior
- start with LoginService and SessionManager
- update related tests only if needed

Non-goals:
- refactoring the whole authentication module
- changing security configuration
- changing production config files
- touching unrelated user-management code

Now the agent has a much clearer task. It can still ask to expand the scope, but it should not silently turn a focused bug fix into a broad project investigation.

This gives the AI agent a much better box to work inside.


The Workflow I Use

I use a lightweight internal spec workflow with this structure:

spec/
  WORKFLOW.md
  project.md
  current/
    agenttokeninsight.md
  changes/
    .gitkeep
  archive/

And project-local Codex skills:

.codex/skills/
  spec-propose/
    SKILL.md
  spec-apply/
    SKILL.md
  spec-sync/
    SKILL.md
  spec-archive/
    SKILL.md
  spec-explore/
    SKILL.md

The commands are intentionally simple:

$spec-propose add-feature-name
$spec-apply add-feature-name
$spec-sync add-feature-name
$spec-archive add-feature-name
$spec-explore

These are not universal built-in commands. In my project, they are project-local Codex skills that enforce the workflow.

The important point is not the exact command name. The important point is the lifecycle.


Influences

This workflow is my own lightweight version for this project, but it is influenced by the broader Spec-Driven Development ecosystem.

Tools and projects such as OpenSpec and GitHub Spec Kit helped popularize the idea of using structured specs to guide AI coding agents.

I did not want to copy a full framework into this project. Instead, I kept the parts that matched my needs: small proposals, explicit non-goals, living specs, and archived change history.


The Core Folder Roles

spec/project.md

This is the project guidance file.

It contains things like:

- project purpose
- tech stack
- constraints
- naming decisions
- architectural boundaries
- things not to add unless explicitly requested

For AgentTokenInsight, this includes guidance like:

- Java 25
- Gradle Kotlin DSL
- Picocli
- Jackson YAML and JSON
- JUnit 5
- AssertJ
- deterministic approximate token estimates
- no Spring Boot, no web dashboard, no database unless later specified

This file helps the agent understand the project before touching code.

spec/current/agenttokeninsight.md

This is the living spec.

The living spec is not the dream, the roadmap, or the original proposal.

It is the current observable behavior of the product.

If the code changes user-visible behavior, the living spec should change too. If the code only refactors internals, the living spec probably should not change.

For example:

AgentTokenInsight must support loading configuration from agenttoken.yml.
AgentTokenInsight must support scanning a repository from a root directory.
AgentTokenInsight must support generating Markdown reports.
AgentTokenInsight must support JSON output from agenttoken scan --format json.

This file is the product truth.

spec/changes/<change-name>/

Every active change gets its own folder:

spec/changes/add-scan-insights/
  proposal.md
  design.md
  tasks.md
  spec.md

This is where new work is planned before implementation.

spec/archive/

When a change is completed, it moves to archive.

Archive is history. Do not rewrite it casually.


What Goes into a Change?

A typical change contains four files.

proposal.md

The proposal answers:

What are we changing?
Why are we changing it?
What is in scope?
What is out of scope?

Example:

# add-cli-json-format-option

## Summary
Add a --format option to agenttoken scan so the CLI can render either Markdown or JSON output.

## Why
Human users need Markdown by default, while automation needs JSON.

## Scope
- agenttoken scan continues to print Markdown by default
- agenttoken scan --format markdown prints Markdown
- agenttoken scan --format json prints JSON
- invalid format values fail clearly

## Non-goals
- changing repository scanning behavior
- changing budget validation behavior
- writing reports to files
- adding GitHub Actions

The non-goals are extremely important. They reduce agent drift.

design.md

The design explains the approach.

It does not need to be long.

Example:

# Design

The scan command should treat report format as an output concern only.

The command should map:
- markdown -> MarkdownReportGenerator
- json -> JsonReportGenerator

The scanner and validator must not change.

tasks.md

This is the implementation checklist.

Example:

# Tasks

- [ ] Add --format option to scan.
- [ ] Keep Markdown as default.
- [ ] Route JSON output through JsonReportGenerator.
- [ ] Fail clearly on unsupported format values.
- [ ] Add tests for default Markdown.
- [ ] Add tests for JSON output.
- [ ] Add tests for invalid format.

spec.md

This describes observable behavior.

Example:

# AgentTokenInsight Scan Format Selection

## Requirement 1: AgentTokenInsight must support selecting scan report format from the CLI

The CLI must provide a --format option for agenttoken scan.
When the option is omitted, the command must print Markdown by default.
When --format markdown is provided, the command must print Markdown.
When --format json is provided, the command must print JSON.
The command must fail clearly when an unsupported format value is provided.

### Scenario: Markdown is the default
Given a user runs agenttoken scan without specifying --format
When the command completes successfully
Then the CLI prints Markdown output

### Scenario: JSON is requested explicitly
Given a user runs agenttoken scan --format json
When the command completes successfully
Then the CLI prints JSON output

This gives the agent a testable contract.


Apply: Implementation Comes After Review

After the change is reviewed, implementation starts.

The apply step should read:

spec/project.md
spec/changes/<change-name>/proposal.md
spec/changes/<change-name>/design.md
spec/changes/<change-name>/tasks.md
spec/changes/<change-name>/spec.md

Then it should implement only that scope.

The rule is:

Do not implement non-goals.
Do not sync living specs automatically.
Do not archive automatically.

That separation matters.

Implementation is one step.
Spec sync is another.
Archiving is another.

This keeps the workflow controlled.


Sync: Updating the Living Spec

After code is implemented and tested, the living spec must be updated if product behavior changed.

This is the step many teams skip.

They write a design once, implement the code, and then the design becomes stale.

That is not a living spec. That is documentation debt.

For example, after adding JSON output, the current spec should now include:

AgentTokenInsight must support agenttoken scan --format json.

But it should not include implementation notes that are no longer relevant.

The living spec should describe the product, not the temporary development plan.


Archive: Keeping History Without Polluting Current Truth

Once a change is implemented and the living spec is updated, the change folder moves to archive:

spec/changes/add-cli-json-format-option/

becomes:

spec/archive/add-cli-json-format-option/

The archive is useful because it answers:

Why did we add this?
What was the original scope?
What were the non-goals?
What tasks were done?

But archive is not the current product truth.

The living spec is.


When Should You Update the Living Spec?

You should update the living spec when behavior changes.

Good reasons to update:

- new CLI command
- new config option
- changed output format
- changed validation behavior
- changed error handling
- changed public API
- changed generated report structure

You probably do not need to update the living spec for:

- internal refactor with no behavior change
- test-only cleanup
- formatting changes
- dependency patch with no observable behavior change
- README typo fix

A simple rule:

If a user, caller, or integrator can observe the change, update the living spec.


SDD and Token Usage

Spec-Driven Development can reduce token waste when working with AI agents.

Not because specs are free. They are not. Specs also consume tokens.

But SDD reduces the expensive kind of waste:

- repeated explanation
- agent misunderstanding
- unrelated code changes
- rework
- large context dumps
- rediscovering project decisions
- debugging wrong assumptions

Without SDD, you may keep writing prompts like:

No, don't change the CLI.
No, don't add JSON yet.
No, don't touch scanner behavior.
No, the config file is agenttoken.yml now.
No, Java packages should stay under com.agenttoken.

With SDD, these decisions live in project guidance and current specs.

That makes prompts shorter.

Instead of explaining the whole project again, you can say:

$spec-apply add-scan-insights
Implement only this approved change.

The agent has a smaller and clearer target.

SDD Uses Tokens Upfront to Save Tokens Later

This is the tradeoff.

SDD adds upfront writing:

proposal.md
design.md
tasks.md
spec.md

But it can save tokens later by reducing:

misimplementation
backtracking
scope creep
review noise
repeated context

For small changes, the overhead may not be worth it.

For medium or complex changes, it often is.


Tradeoffs of Spec-Driven Development

SDD is not magic. It has costs.

It also does not make bad intent safe. If the spec is vague, wrong, or too broad, the agent may still produce the wrong thing - just with more confidence.

Benefits

- clearer feature boundaries
- better AI agent control
- fewer unrelated changes
- better review process
- current product behavior is documented
- easier onboarding
- easier release preparation
- more stable architecture decisions

Costs

- more files
- more process
- slower start for tiny changes
- living specs require discipline
- bad specs can mislead agents
- too much detail can waste tokens

The goal is not to write more documentation.

The goal is to write just enough structure to make implementation safer and faster.


When SDD Makes Sense

SDD is a good fit when:

- you are using an AI coding agent heavily
- the project has multiple features
- scope control matters
- behavior should remain stable
- you want repeatable implementation flow
- multiple sessions or contributors will touch the project
- release quality matters

It is especially useful for:

- CLI tools
- libraries
- APIs
- developer tools
- automation tools
- products with evolving behavior

AgentTokenInsight is a good example because it has:

- CLI commands
- config files
- validators
- reports
- packaging
- user-visible behavior

That kind of project benefits from a living spec.


When SDD Is Too Much

SDD may not be worth it for:

- a 30-line script
- a quick local experiment
- throwaway prototype
- one-time data conversion
- purely visual exploration

For those, a simple checklist or short prompt is enough.

Do not turn SDD into ceremony.

The point is control, not bureaucracy.


Example: AgentTokenInsight Feature Flow

Here is a real-style flow.

Step 1: Propose

$spec-propose add-scan-insights

Creates:

spec/changes/add-scan-insights/
  proposal.md
  design.md
  tasks.md
  spec.md

Proposal:

## Summary
Add actionable insights to scan reports.

## Why
Users need to understand why a scope is too large and what to do next.

## Scope
- Add Insights section to Markdown reports.
- Add insights array to JSON reports.
- Generate deterministic insights from scan and validation results.

## Non-goals
- AI-generated recommendations
- model-specific tokenization
- changing scanner behavior
- changing CLI commands

Step 2: Review

Before coding, review the spec.

Ask:

Is the scope small?
Are non-goals clear?
Is the behavior testable?
Could this accidentally change existing behavior?

Step 3: Apply

$spec-apply add-scan-insights

The implementation should only do what the change says.

Step 4: Test

./gradlew test

Step 5: Sync

$spec-sync add-scan-insights

Update:

spec/current/agenttokeninsight.md

Step 6: Archive

$spec-archive add-scan-insights

Move the completed change to:

spec/archive/add-scan-insights/

This gives us both current truth and historical context.


How This Helps AI Agent Work

The most important benefit is not documentation.

The most important benefit is controlled delegation.

When working with an AI coding agent, you are delegating implementation. SDD gives the agent a contract.

Without SDD:

“Add scan insights.”

With SDD:

“Implement the approved add-scan-insights change.
Read proposal, design, tasks, and spec.
Do not implement non-goals.
Do not change scanner behavior.
Add tests.”

That is a much better instruction.

It reduces ambiguity.

It also gives you better review points:

Review proposal before implementation.
Review implementation before sync.
Review living spec before archive.

The Future of SDD

I think SDD will become more important as AI coding agents become more capable.

Why?

Because the more powerful the agent, the more important the boundary becomes.

A weak assistant needs help writing code.
A strong assistant needs clear constraints.

Future development may look less like:

developer writes every line of code

and more like:

developer defines intent, constraints, tests, and product boundaries
agent implements
developer reviews and steers

In that world, specs are not bureaucracy.

Specs are the interface between human intent and agent execution.

A good spec tells the agent:

what to do
why it matters
what not to do
how success is observed
where current truth lives

That is exactly the kind of structure AI development needs.


Practical Rules for Lightweight SDD

1. One change at a time

Do not combine unrelated changes.

Good:

add-json-output

Bad:

add-json-output-and-github-actions-and-refactor-scanner

2. Always write non-goals

Non-goals are often more important than scope.

They stop agent drift.

3. Keep design short

A design does not need to be a novel.

A few clear paragraphs are enough.

4. Requirements should be observable

Avoid vague specs like:

The system should be better.

Prefer:

When agenttoken scan --format json runs, the output must be valid JSON and include scannedFileCount.

5. Do not sync before implementation

The living spec should describe current behavior, not planned behavior.

6. Archive is history

Do not rewrite old archived changes just because the product was renamed later.

If the current behavior changed, update current spec.
If history says something old, let it remain history.

7. Use SDD where it pays off

Use it for product behavior.

Do not force it for every typo.


Conclusion

Spec-Driven Development is not about writing documents for the sake of documents.

It is about creating a lightweight control system for software changes, especially when AI coding agents are involved.

For me, the useful pattern is:

propose → review → apply → test → sync → archive

The propose step may create proposal.md, design.md, tasks.md, and spec.md. The important part is that implementation starts only after the scope is clear enough to review.

This gives the AI agent enough structure to work effectively without giving it unlimited freedom to reshape the project.

In AgentTokenInsight, this workflow helped keep changes small, reviewable, and aligned with the product goal.

The biggest lesson is simple:

The stronger the coding agent becomes, the more valuable clear specs become.

Not because the agent is weak.

Because the agent is powerful.

And powerful tools need boundaries.

Sunday

animated square stroke - css

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">

  <path fill="white" stroke="black" stroke-width="4"  class="path" d="M 10 10 H 90 V 90 H 10 L 10 10"/>

  <!-- Points -->
  <circle cx="10" cy="10" r="2" fill="red"/>
  <circle cx="90" cy="90" r="2" fill="red"/>
  <circle cx="90" cy="10" r="2" fill="red"/>
  <circle cx="10" cy="90" r="2" fill="red"/>
</svg>

.path {
  stroke-dasharray: 320;
  stroke-dashoffset: 320;
  animation: dash 8s linear  infinite;
}

@keyframes dash {
  from {
    stroke-dashoffset: 320;
  }

  to {
    stroke-dashoffset: 0;
  }
}

Screenshot:



 



An example of converting object to an array in javascript


const ingredients = {
 salad: 2,
 tomato: 1,
 cheese: 3,
 meat: 2
}

const transformedIngredients = Object.keys(ingredients)
  .map(iKey => {
     return [...Array(ingredients[iKey])].map((_, i) => {
       return iKey ;
   });
}); 

console.log(transformedIngredients.toString());
the output is: salad,salad,tomato,cheese,cheese,cheese,meat,meat

**Object.keys(ingredients) returns key list:   salad,tomato,cheese,meat
**Array(ingredients[iKey])   returns the value of the given key like salad. for instance,the output for salad: Array(2). so the second map loops 2 time and returns salad twice.

enable css loader >> unknown property 'localIdentName' error

versions >>  "css-loader":"3.4.2"

firstly looking: (in the project created by create-react-app which has been ejected)
{
  test: cssRegex,
  exclude: cssModuleRegex,
  use: getStyleLoaders({
 importLoaders: 1,
 sourceMap: isEnvProduction && shouldUseSourceMap,
  }),
  sideEffects: true,
},
I tried: (this one is the older config)
{
  test: cssRegex,
  exclude: cssModuleRegex,
  use: getStyleLoaders({
 importLoaders: 1,
 modules: true,
 localIdentName: "[name]__[local]___[hash:base64:5]",
 sourceMap: isEnvProduction && shouldUseSourceMap,
  }),
  sideEffects: true,
},
i got following error:
./src/index.css (./node_modules/css-loader/dist/cjs.js??ref--6-oneOf-3-1!./node_modules/postcss-loader/src??postcss!./src/index.css)
ValidationError: Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
 - options has an unknown property 'localIdentName'. These properties are valid:
   object { url?, import?, modules?, sourceMap?, importLoaders?, localsConvention?, onlyLocals?, esModule? }

FIX:
 {
  test: cssRegex,
  exclude: cssModuleRegex,
  use: getStyleLoaders({
 importLoaders: 1,
 modules: {
   localIdentName: "[name]__[local]___[hash:base64:5]",
 }, 
 sourceMap: isEnvProduction && shouldUseSourceMap,
  }),
  sideEffects: true,
},

Saturday

How to copy given text to the clipboard in react with typescript


import React from 'react';
import { LightTooltip } from '../tooltip/lightTooltip';

interface IOwnProps {
  messageToBeCopied: string | null;
}

export default class CopyToClipboard extends React.Component<IOwnProps> {

  render() {
    const { messageToBeCopied, children } = this.props;
    let isCopySuccess = false;

    if (messageToBeCopied) {
      try {
        const textField = document.createElement('textarea');
        textField.innerText = messageToBeCopied;
        document.body.appendChild(textField);
        textField.select();

        isCopySuccess = document.execCommand('copy');
        textField.remove();

      } catch (e) {
        isCopySuccess = false;
      }

      return (
        <LightTooltip
          leaveDelay={1}
          leaveTouchDelay={1}
          title={isCopySuccess === false ? 'Failed to copy.' : 'Copied.'}
        >
          {children as React.ReactElement}
        </LightTooltip>
      );
    }

    return <>{children}</>;
  }
}

import { withStyles, Theme } from '@material-ui/core/styles';
import Tooltip from '@material-ui/core/Tooltip';

export  const LightTooltip = withStyles((theme: Theme) => ({
  tooltip: {
    backgroundColor: theme.palette.common.white,
    color: 'rgba(0, 0, 0, 0.87)',
    boxShadow: theme.shadows[1],
    fontSize: 12,
    fontFamily: 'Rubik-Regular',
  },
}))(Tooltip);

import React from 'react';
import CopyToClipboard from '../copyToClipboard/copyToClipboard';

interface IState {
  clipboardMessage: string | null;
}

class Example extends React.Component<null, IState> {
  constructor(props) {
    super(props);
    this.state = {
      clipboardMessage: null,
    };
  }

  setClipboardMessage() {
    const message = 'Message for copy to clipboard';

    this.setState({
      clipboardMessage: message,
    });
  }

  render() {
    return (
      <CopyToClipboard messageToBeCopied={this.state.clipboardMessage}>
        <a onClick={() => this.setClipboardMessage()}>Copy</a>
      </CopyToClipboard>
    );
  }
}

export default Example;
Example screenshot after clicking the text:

How to add fonts into react project

Define your font as follows in your app.scss/app.css :
(You can use your fonts as named in font-family.)
@font-face {
  font-family: 'Rubik-Regular';
  src: local('Rubik'), url(../static/font/Rubik/Rubik-Regular.ttf) format('truetype');
}

@font-face {
  font-family: 'Rubik-Medium';
  src: local('Rubik'), url(../static/font/Rubik/Rubik-Medium.ttf) format('truetype');
}

Add font files into src/static/font/..



















Convert currency code to symbol in react with typescript


import { currencyMap } from './currencyMap';

export default function  getSymbolFromCurrencyCode(currencyCode : string) {
  const code = currencyCode && currencyCode.toUpperCase();
  if (!currencyMap.hasOwnProperty(code)) {
    return currencyCode;
  }
  return currencyMap[code];
}

export const currencyMap = {
  'AED': 'د.إ',
  'AFN': '؋',
  'ALL': 'L',
  'AMD': '֏',
  'ANG': 'ƒ',
  'AOA': 'Kz',
  'ARS': '$',
  'AUD': '$',
  'AWG': 'ƒ',
  'AZN': '₼',
  'BAM': 'KM',
  'BBD': '$',
  'BDT': '৳',
  'BGN': 'лв',
  'BHD': '.د.ب',
  'BIF': 'FBu',
  'BMD': '$',
  'BND': '$',
  'BOB': '$b',
  'BRL': 'R$',
  'BSD': '$',
  'BTC': '฿',
  'BTN': 'Nu.',
  'BWP': 'P',
  'BYR': 'Br',
  'BYN': 'Br',
  'BZD': 'BZ$',
  'CAD': '$',
  'CDF': 'FC',
  'CHF': 'CHF',
  'CLP': '$',
  'CNY': '¥',
  'COP': '$',
  'CRC': '₡',
  'CUC': '$',
  'CUP': '₱',
  'CVE': '$',
  'CZK': 'Kč',
  'DJF': 'Fdj',
  'DKK': 'kr',
  'DOP': 'RD$',
  'DZD': 'دج',
  'EEK': 'kr',
  'EGP': '£',
  'ERN': 'Nfk',
  'ETB': 'Br',
  'ETH': 'Ξ',
  'EUR': '€',
  'FJD': '$',
  'FKP': '£',
  'GBP': '£',
  'GEL': '₾',
  'GGP': '£',
  'GHC': '₵',
  'GHS': 'GH₵',
  'GIP': '£',
  'GMD': 'D',
  'GNF': 'FG',
  'GTQ': 'Q',
  'GYD': '$',
  'HKD': '$',
  'HNL': 'L',
  'HRK': 'kn',
  'HTG': 'G',
  'HUF': 'Ft',
  'IDR': 'Rp',
  'ILS': '₪',
  'IMP': '£',
  'INR': '₹',
  'IQD': 'ع.د',
  'IRR': '﷼',
  'ISK': 'kr',
  'JEP': '£',
  'JMD': 'J$',
  'JOD': 'JD',
  'JPY': '¥',
  'KES': 'KSh',
  'KGS': 'лв',
  'KHR': '៛',
  'KMF': 'CF',
  'KPW': '₩',
  'KRW': '₩',
  'KWD': 'KD',
  'KYD': '$',
  'KZT': 'лв',
  'LAK': '₭',
  'LBP': '£',
  'LKR': '₨',
  'LRD': '$',
  'LSL': 'M',
  'LTC': 'Ł',
  'LTL': 'Lt',
  'LVL': 'Ls',
  'LYD': 'LD',
  'MAD': 'MAD',
  'MDL': 'lei',
  'MGA': 'Ar',
  'MKD': 'ден',
  'MMK': 'K',
  'MNT': '₮',
  'MOP': 'MOP$',
  'MRO': 'UM',
  'MRU': 'UM',
  'MUR': '₨',
  'MVR': 'Rf',
  'MWK': 'MK',
  'MXN': '$',
  'MYR': 'RM',
  'MZN': 'MT',
  'NAD': '$',
  'NGN': '₦',
  'NIO': 'C$',
  'NOK': 'kr',
  'NPR': '₨',
  'NZD': '$',
  'OMR': '﷼',
  'PAB': 'B/.',
  'PEN': 'S/.',
  'PGK': 'K',
  'PHP': '₱',
  'PKR': '₨',
  'PLN': 'zł',
  'PYG': 'Gs',
  'QAR': '﷼',
  'RMB': '¥',
  'RON': 'lei',
  'RSD': 'Дин.',
  'RUB': '₽',
  'RWF': 'R₣',
  'SAR': '﷼',
  'SBD': '$',
  'SCR': '₨',
  'SDG': 'ج.س.',
  'SEK': 'kr',
  'SGD': '$',
  'SHP': '£',
  'SLL': 'Le',
  'SOS': 'S',
  'SRD': '$',
  'SSP': '£',
  'STD': 'Db',
  'STN': 'Db',
  'SVC': '$',
  'SYP': '£',
  'SZL': 'E',
  'THB': '฿',
  'TJS': 'SM',
  'TMT': 'T',
  'TND': 'د.ت',
  'TOP': 'T$',
  'TRL': '₤',
  'TRY': '₺',
  'TL': '₺',
  'TTD': 'TT$',
  'TVD': '$',
  'TWD': 'NT$',
  'TZS': 'TSh',
  'UAH': '₴',
  'UGX': 'USh',
  'USD': '$',
  'UYU': '$U',
  'UZS': 'лв',
  'VEF': 'Bs',
  'VND': '₫',
  'VUV': 'VT',
  'WST': 'WS$',
  'XAF': 'FCFA',
  'XBT': 'Ƀ',
  'XCD': '$',
  'XOF': 'CFA',
  'XPF': '₣',
  'YER': '﷼',
  'ZAR': 'R',
  'ZWD': 'Z$',
}
Example:
import getSymbolFromCurrencyCode from '../commons/currencySymbol/currencySymbolMap';

...
getSymbolFromCurrencyCode('TRY');
...

Typescript index signature example


import Example1 from '../../components/……....';
import Example2 from '../../components/…....';


interface ExampleInfos {
  name: any;
  param1: string;
  param2?: boolean;
}


interface ExampleLayout {
  [key:string]: ExampleInfos;
}


export const exampleMap: ExampleLayout = {
  example1: {
    name: Example1,
    param1: 'medium',
  },
  example2: {
    name: Example2,
    param1: 'medium',
    param2: false,
  },
}


import { exampleMap } from './exampleMap';

...
 const myMap= exampleMap['example1'];
 const myComponent = myMap && myMap.name;
...