<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cristian Bonomo</title>
    <description>The latest articles on DEV Community by Cristian Bonomo (@cristianbonomo).</description>
    <link>https://dev.to/cristianbonomo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F553558%2F00d23b7a-0e1b-442f-8d2b-8004af68d222.jpg</url>
      <title>DEV Community: Cristian Bonomo</title>
      <link>https://dev.to/cristianbonomo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cristianbonomo"/>
    <language>en</language>
    <item>
      <title>Building a Harness From Zero</title>
      <dc:creator>Cristian Bonomo</dc:creator>
      <pubDate>Mon, 14 Sep 2026 13:08:21 +0000</pubDate>
      <link>https://dev.to/cristianbonomo/building-a-harness-from-zero-17if</link>
      <guid>https://dev.to/cristianbonomo/building-a-harness-from-zero-17if</guid>
      <description>&lt;p&gt;We're going to build a harness from zero, explaining step by step how to do it and why.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Harness
&lt;/h2&gt;

&lt;p&gt;A harness is the set of files, conventions, and rules that give an AI agent everything it needs to operate in a repository without depending on someone feeding it context in a chat. It defines how the agent starts, what it can and can't do, how it knows a task is done, and how it leaves a record of what it did so the next session (another agent, or you) can continue without rebuilding everything from scratch.&lt;/p&gt;

&lt;p&gt;The underlying idea is simple: if the criterion for "done" only lives in your head, or in a chat message that's going to disappear, any agent (or any person) who continues the work is going to make different decisions than you would. A harness takes those decisions out of the chat and puts them in the repository, as the single source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the Sandbox App
&lt;/h2&gt;

&lt;p&gt;We're going to build a minimal sandbox project, since it's not the important part here — what matters is what we build around it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project is a simple ledger that lets you record credit and debit operations, search by date, and get an account's balance, built on top of a LinkedList and a Map.&lt;/p&gt;

&lt;p&gt;We'll also need a verification mechanism: check and test scripts the harness can run to validate its own changes.&lt;/p&gt;

&lt;p&gt;Once we have the base of our ledger, let's set up the initial structure of our harness.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Structure
&lt;/h2&gt;

&lt;p&gt;We need an entry point for our harness — an AGENTS.md file (or CLAUDE.md, in Claude Code's case). We'll use &lt;code&gt;/init&lt;/code&gt; in Claude to generate an initial CLAUDE.md, and then modify it.&lt;/p&gt;

&lt;p&gt;Reference commit with the generated base file: &lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/e0cb08c81b1d92d3c7fc831bdc05f931b683908f#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/e0cb08c81b1d92d3c7fc831bdc05f931b683908f#diff-6ebdb617a8104a7756d0cf36578ab01103dc9f07e4dc6feb751296b9c402faf7&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Editing CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;CLAUDE.md should contain only the instructions the harness needs to operate, plus references to other files the agent can reach if it needs them. It shouldn't go over 100 lines.&lt;/p&gt;

&lt;h4&gt;
  
  
  Sections
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Initial description: briefly tells the agent the purpose of the file.&lt;/li&gt;
&lt;li&gt;Operating Loop: the sequence of steps every agent must follow.&lt;/li&gt;
&lt;li&gt;Rules: the set of constraints the agent must respect while working in this repository, following the operating loop.&lt;/li&gt;
&lt;li&gt;Required files: files the harness needs to function.&lt;/li&gt;
&lt;li&gt;Completion gate: the explicit rule by which the agent knows it has completed the task.&lt;/li&gt;
&lt;li&gt;Before you stop: the final steps for handing off the task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our CLAUDE.md had architecture details in it — let's move those to a new ARCHITECTURE.md file.&lt;/p&gt;

&lt;p&gt;You can see the state after that refactor in this commit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/7ef78c14c334748a29a7b22e1aaf67d77a993e54" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/7ef78c14c334748a29a7b22e1aaf67d77a993e54&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Startup Script
&lt;/h3&gt;

&lt;p&gt;Now we need to add our startup script, which takes care of initializing everything related to the project: installing dependencies, running verification scripts, and the start command, all in a single step — so the Operating Loop has something real to run.&lt;/p&gt;

&lt;p&gt;You can see it in this commit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/bed716002edb4678412f85a4544e17fbc912018e" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/bed716002edb4678412f85a4544e17fbc912018e&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Feature List
&lt;/h4&gt;

&lt;p&gt;Once this script is validated, we can move on to structuring feature_list.json. But first, we need to define what our ledger-cli's features will be. For this example, they'll be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;history(account)&lt;/code&gt;&lt;/strong&gt; — a chronological listing with a running balance per account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correction/reversal (void)&lt;/strong&gt; — a past transaction is never deleted or edited; instead, an entry is added that reverses it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transfer between accounts&lt;/strong&gt; — move money from one account to another as a single atomic operation (debit on the source + credit on the destination).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's define the structure of our feature_list.json. Each feature needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;id: unique identifier&lt;/li&gt;
&lt;li&gt;priority: an integer — the lower it is, the higher the priority&lt;/li&gt;
&lt;li&gt;area: which part of the app it touches&lt;/li&gt;
&lt;li&gt;title: a short description of the task&lt;/li&gt;
&lt;li&gt;user_visible_behavior: what the user should see once the feature is complete&lt;/li&gt;
&lt;li&gt;status: the task's state, one of:

&lt;ul&gt;
&lt;li&gt;not_started: not yet started&lt;/li&gt;
&lt;li&gt;in_progress: feature currently being worked on. Only one at a time&lt;/li&gt;
&lt;li&gt;blocked: feature blocked by a known issue or unmet precondition&lt;/li&gt;
&lt;li&gt;passing: verification has passed and evidence has been saved&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;verification: step-by-step instructions to confirm it works&lt;/li&gt;
&lt;li&gt;evidence: proof that verification passed, filled in by the agent&lt;/li&gt;
&lt;li&gt;notes: extra content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the feature_list.json in this commit:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/50fdc573689144e858cba83226cdeeca059d2f3b" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/50fdc573689144e858cba83226cdeeca059d2f3b&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Progress
&lt;/h3&gt;

&lt;p&gt;Now we need a file where we can log every session and task we work through. For this, we use claude-progress.md (or just PROGRESS.md).&lt;/p&gt;

&lt;p&gt;You can see it here: &lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/19cf16927c6fcfa83511010a61fc37945182021e" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/19cf16927c6fcfa83511010a61fc37945182021e&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Our Harness for the First Time
&lt;/h2&gt;

&lt;p&gt;With this, we now have the base of our harness. Let's run our first feature. The history feature needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;filter by a single account&lt;/li&gt;
&lt;li&gt;sort that account's transactions by date&lt;/li&gt;
&lt;li&gt;walk that sorted list accumulating the balance, storing the running balance at every step, not just at the end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, we'll just leave it the information it has in feature_list.json and see what it does. We open Claude in a terminal and give it the following prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Follow the operating loop from CLAUDE.md and work on the top priority feature pending on @harness/feature_list.json&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Commit with the result of that run:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/9e7fbe53907d0a0227de1ec54084b01b2cbb6632" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/9e7fbe53907d0a0227de1ec54084b01b2cbb6632&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking at the result, we can see the harness completed the task, but took quite a few liberties with how the code was written. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toArray&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;TransactionType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CREDIT&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice it used an anonymous type, when by convention it would've been better to declare a new &lt;code&gt;AccountHistoryEntry { transaction: Transaction; runningBalance: number }&lt;/code&gt; interface in src/types.ts, following the convention we'd been building toward. But we never wrote that convention down anywhere, and since the repository is the single source of truth, it implemented it however it saw fit.&lt;/p&gt;

&lt;p&gt;To improve this, we have a few options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Separate the implementing agent from the evaluator: there's a documented bias (self-evaluation bias) where, when an agent is asked to evaluate its own work, it systematically gives more positive evaluations than an independent observer would. The fix is to use a separate agent to review the implementer's work, against a rubric that spells out exactly what conditions must be met before marking a task as passing.&lt;/li&gt;
&lt;li&gt;Sprint contract: we need a contract defined before implementation that spells out what to change, what not to change, and the verification standards.&lt;/li&gt;
&lt;li&gt;New conventions file: we'll add a new CONVENTIONS.md file listing the code conventions we want to respect and maintain. In this project it ended up with simple, concrete rules: complex return types belong in src/types/types.ts (not inline), a saved transaction is never mutated, every new Ledger method needs a happy-path test and an edge-case test, no CLI framework. Nothing exotic — but exactly the kind of thing an agent with no memory of the previous session has no way of guessing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the files in this commit: &lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/261dd0fb7a3c2dc596fcd06693770ef525d9173d" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/261dd0fb7a3c2dc596fcd06693770ef525d9173d&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's implement the next task and see the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Second Task
&lt;/h2&gt;

&lt;p&gt;Before dispatching the implementer, we wrote this task's sprint contract (void). What's interesting isn't just the Scope section, but the Exclusions one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  Exclusions (explicitly out of scope for this sprint)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Behavior when &lt;code&gt;id&lt;/code&gt; does not exist — undefined, not required, not tested.&lt;/li&gt;
&lt;li&gt;Preventing or detecting double-voiding the same transaction — undefined, not required, not tested.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Explicitly saying what &lt;em&gt;not&lt;/em&gt; to solve is just as important as saying what to solve — without this, an agent might "improve" the task by adding error handling nobody asked for, or worse, leave it half-done.&lt;/p&gt;

&lt;p&gt;Using our Claude terminal in the project, we run &lt;code&gt;/clear&lt;/code&gt; and then:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;use the implementer subagent for ledger-002&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Commit with the implementation: &lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/d8d3d187a0f93a8944886ead3ae2802b0e37f04c" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/d8d3d187a0f93a8944886ead3ae2802b0e37f04c&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then, to have it evaluated:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;use the evaluator subagent for ledger-002&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The evaluator doesn't give a free-form verdict — it fills out evaluator-rubric.md, which scores 6 dimensions from 0 to 2: correctness (does it do what feature_list.json asks for?), contract fidelity (did it respect the names and locations from the sprint contract, not just "does it work"?), verification (did it re-run the checks itself, or trust the implementer's notes?), scope discipline (did it stay within what the contract allowed?), reliability (does it survive a &lt;code&gt;rm -rf dist &amp;amp;&amp;amp; ./init.sh&lt;/code&gt; from clean?), and handoff readiness (was progress properly recorded?). Only if everything scores 2 (or there's a documented exception) is the verdict Accept.&lt;/p&gt;

&lt;p&gt;Now we can see that different agents were used, conventions were respected, the sprint contract was implemented, and the rubric was completed. But the progress file didn't record the commits, and we still aren't really validating that the app stays in a consistent state and runs correctly, beyond the tests.&lt;/p&gt;

&lt;p&gt;To fix that, we need to add a file listing the checks the evaluator agent has to run as part of the passing requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clean State Checklist
&lt;/h3&gt;

&lt;p&gt;The underlying problem is that "tests pass" isn't the same as "the repository is left in a consistent state." An agent can leave the build broken, progress badly recorded, or stray work files behind, and the tests for that specific feature will still pass just fine. The clean state checklist is a short list of non-negotiable conditions (build compiles, tests pass, recorded progress matches what actually happened, no leftover temporary artifacts) that the evaluator has to confirm one by one before considering the session closed — it's not enough for just the feature at hand to work.&lt;/p&gt;

&lt;p&gt;One detail worth calling out: the check that recorded progress is accurate has to happen &lt;em&gt;after&lt;/em&gt; committing, not before — the commit hash depends on the entire tree, including the progress file itself, so you can't verify a reference that doesn't exist yet.&lt;/p&gt;

&lt;p&gt;We implemented the clean-state-checklist.md file in the harness. And while we're at it, remember we'd blocked the evaluator agent from committing — that has to change, because if the evaluator is the one running the final checklist, it also has to be the one closing the session with the commit.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/bdf3b8a1122dd843109dbd1db78b9e8db55348bf" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/bdf3b8a1122dd843109dbd1db78b9e8db55348bf&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Third Task
&lt;/h2&gt;

&lt;p&gt;With these changes in place, we can now implement the third task (ledger-003, transfers). Since we already wrote the sprint contract before dispatching it, the prompt is straightforward after a &lt;code&gt;/clear&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;implement the next task by priority&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/51fa2cbadc51773ec72d167d83fb58347b6a6d7b" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/51fa2cbadc51773ec72d167d83fb58347b6a6d7b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This time the whole cycle (contract → implementer → evaluator → checklist) held up without any manual adjustment on my part. The difference from the first task isn't that the agent got better — it's that this time the design decisions were already in the repository before it started writing code, instead of living in my head.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Handoff Between Agents
&lt;/h2&gt;

&lt;p&gt;We now have our three features implemented, but our agents left loose fixes and validations scattered along the way. We're going to tackle error handling and validation across the flows. But to do that, we need to split the implementation into subtasks — and that's where we need a safe mechanism for agents to record what they did, so each agent can pick up exactly where the last one left off.&lt;/p&gt;

&lt;p&gt;We add a new session-handoff file to handle this, update our CLAUDE.md and agent definitions, add the task to the feature list, and write our sprint contract.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/555b93016eec16974985ee0d1b16cfdd6e9a8e0e" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/555b93016eec16974985ee0d1b16cfdd6e9a8e0e&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can start implementing our task. Session A builds the shared mechanism (the error hierarchy) and proves it out on a single flow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are the implementer. Work on ledger-004 following harness/sprint-contracts/ledger-004-validation-errors.md —&lt;br&gt;
but ONLY the "Session A" part of the Delivery Plan: create a complete src/errors.ts (the 4-exception hierarchy)&lt;br&gt;
and implement/test only record()'s guard. Don't touch transfer() or void(). When you're done, write&lt;br&gt;
harness/session-handoff.md instead of harness/claude-progress.md, and leave the feature in_progress.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/e854260551a37c401e9121154bc89550c3211b40" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/e854260551a37c401e9121154bc89550c3211b40&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we move on to Session B. This is the key point of the whole experiment: it starts with a &lt;code&gt;/clear&lt;/code&gt;, with nothing from Session A's chat, and the only context it receives is session-handoff.md:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are the implementer. Start by reading harness/session-handoff.md — don't assume anything else about what the&lt;br&gt;
previous session did beyond what that file says. Follow the rest of CLAUDE.md's Operating Loop and finish&lt;br&gt;
ledger-004 according to the "Session B" part of harness/sprint-contracts/ledger-004-validation-errors.md: transfer(),&lt;br&gt;
void(), and the CLI's try/catch. Write harness/claude-progress.md as usual.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/002dc66c80146165f5a20aafff97886789a7167f#diff-d5b7d5fb05719d146f8dc4f0106425539454fdfeea858de48916db3dc34f3142" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/002dc66c80146165f5a20aafff97886789a7167f#diff-d5b7d5fb05719d146f8dc4f0106425539454fdfeea858de48916db3dc34f3142&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Note: between Session A, Session B, and the evaluator, the intermediate commits were made by me, by hand, in the terminal, orchestrating each dispatch. The implementer is explicitly forbidden from committing — so if you spot a commit "mid-task" anywhere, that's the human operating the harness, not the agent breaking its own rule.)&lt;/p&gt;

&lt;p&gt;Finally, we run the evaluator, which is the one that actually closes out the task with a single commit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You are the evaluator. Evaluate ledger-004 against harness/sprint-contracts/ledger-004-validation-errors.md and&lt;br&gt;
harness/evaluator-rubric.md, reviewing both sessions' work together (A and B) — including whether Session B's code&lt;br&gt;
is genuinely consistent with the classes Session A defined, without redefining them or drifting from them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://github.com/crisbonomodev/ledger-cli/commit/35ffd50101e43dc736be0bd7869e1436c0f9a6a3" rel="noopener noreferrer"&gt;https://github.com/crisbonomodev/ledger-cli/commit/35ffd50101e43dc736be0bd7869e1436c0f9a6a3&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Proved
&lt;/h2&gt;

&lt;p&gt;What matters here isn't that Session B finished the work — it's that it did so without having seen a single line of the chat where Session A was designed. Everything it needed — which error classes already existed, where they lived, what they were called — was in session-handoff.md. And I'm not taking the evaluator's word for it: I checked the code myself afterward — zero redefinitions, zero naming drift. Session B used exactly the classes Session A left behind.&lt;/p&gt;

&lt;p&gt;That's the point of the whole exercise: a harness isn't a tidy set of files, it's the answer to a concrete question — &lt;em&gt;what does the next session need to know so it doesn't repeat or contradict what's already been decided?&lt;/em&gt; When that answer lives in the repo instead of in your memory or in a chat that's about to close, the work becomes pickable-up by anyone: another agent, another session of yours, or someone else on the team. And that habit of not trusting an agent's declared state without checking the evidence yourself is, to me, the one that holds up all the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing It Run
&lt;/h2&gt;

&lt;p&gt;All this talk of contracts, roles, and checklists can sound abstract if you never see the result actually working. After building (&lt;code&gt;npm run build&lt;/code&gt;), here's a real terminal session using the four features we built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js record checking 1 500 &lt;span class="s2"&gt;"Paycheck"&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js record checking debit 120 &lt;span class="s2"&gt;"Groceries"&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js balance checking
&lt;span class="go"&gt;balance of checking: 380

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js &lt;span class="nb"&gt;history &lt;/span&gt;checking
&lt;span class="gp"&gt;2026-09-13 #&lt;/span&gt;1 1 500 - Paycheck | balance: 500
&lt;span class="gp"&gt;2026-09-13 #&lt;/span&gt;2 0 120 - Groceries | balance: 380
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js void 2
&lt;span class="gp"&gt;#&lt;/span&gt;3 voids &lt;span class="c"&gt;#2 - checking 1 120&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js transfer checking savings 100
&lt;span class="gp"&gt;#&lt;/span&gt;4 checking -&amp;gt; &lt;span class="c"&gt;#5 savings - 100 (transferId 4)&lt;/span&gt;
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js balance checking
&lt;span class="go"&gt;balance of checking: 400
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;node dist/index.js balance savings
&lt;span class="go"&gt;balance of savings: 100
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Side note: &lt;code&gt;1&lt;/code&gt; as the second argument to &lt;code&gt;record&lt;/code&gt; means CREDIT — a known, never-fixed bug means comparing it against the string &lt;code&gt;"credit"&lt;/code&gt; doesn't actually work; anything else falls back to DEBIT by default. It's exactly the kind of minor technical debt a sprint contract deliberately leaves out of scope, and that stays documented instead of quietly fixed by nobody's request.)&lt;/p&gt;

&lt;p&gt;The numbers check out: 500 − 120 + 120 (reversed) − 100 (transferred) = 400 in checking, 100 in savings. That's the result of all four features, built across separate agent sessions, working together without friction.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ledger-cli&lt;/code&gt; is a toy project on purpose — the idea was to see the problem and the solution in a full cycle in a couple of hours, not to build something production-grade. Depending on the complexity of the project it's applied to, these files will need to be more or less detailed — and in fact, building and maintaining a harness turns out to be an iterative, ongoing process.&lt;/p&gt;




&lt;p&gt;The full code — CLAUDE.md, sprint contracts, the implementer/evaluator split, all of it — is on GitHub: ledger-cli.&lt;/p&gt;

&lt;p&gt;Curious how others handle this: when two agent sessions can't share chat context, what do you pin outside the conversation so the second one doesn't quietly redefine what the first one built?&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>No todo es console.log()</title>
      <dc:creator>Cristian Bonomo</dc:creator>
      <pubDate>Fri, 24 Dec 2021 17:13:31 +0000</pubDate>
      <link>https://dev.to/cristianbonomo/no-todo-es-consolelog-31hk</link>
      <guid>https://dev.to/cristianbonomo/no-todo-es-consolelog-31hk</guid>
      <description>&lt;p&gt;Cuando empezamos a programar en JavaScript, una de las primeras cosas que aprendemos a hacer es imprimir por consola. &lt;br&gt;
Para esto, nos enseñan el console.log(), el cual nos permite mostrar el mensaje que queramos al ejecutar nuestro código, sea en el navegador o en la terminal que utilicemos.&lt;/p&gt;

&lt;p&gt;Sin embargo, la clase console no está limitada a este único comando, ya que posee varias alternativas y funcionalidades que nos pueden resultar útiles a la hora de debuggear nuestra aplicación.&lt;/p&gt;

&lt;p&gt;Este artículo pretende ser una pequeña guía sobre estos métodos, para tener a mano en caso de que necesitemos algo un poco mas específico que simplemente mostrar algo por pantalla.&lt;/p&gt;

&lt;p&gt;Si tienen ganas de estudiar más a fondo la clase console, y que ocurre por detrás, pueden ver la &lt;a href="https://console.spec.whatwg.org/" rel="noopener noreferrer"&gt;Console API&lt;/a&gt;, la especificación sobre console a la cual los motores de JavaScript se han adaptado para proveer funcionalidades similares.&lt;/p&gt;
&lt;h2&gt;
  
  
  assert()
&lt;/h2&gt;

&lt;p&gt;Con este método, especificaremos una condición, la cual en caso de ser falsa, mostrará un mensaje por consola.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let isActive = false;
let isActiveAgain = true;
console.assert(isActive,'Usuario deshabilitado');
console.assert(isActiveAgain,'Usuario deshabilitado!!!');

-----------------------------------------------------
cbonomo@usuario-ThinkPad-X1-Carbon-Gen-8:~/Javascript/01-fundamentos$ node console.js 
Assertion failed: Usuario deshabilitado
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  clear()
&lt;/h2&gt;

&lt;p&gt;Este método simplemente limpíará la consola en caso de que podamos hacerlo, es otro de los comandos que aprendemos al comienzo.&lt;/p&gt;

&lt;h2&gt;
  
  
  count()
&lt;/h2&gt;

&lt;p&gt;Este método logueará la cantidad de veces que realicemos una llamada a count(), util en caso de que necesitemos establecer un contador para evaluar cuantas veces utilizamos una función.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let user = "";

function saludar() {
    console.count(user);
    return "hola " + user;
}

user = "Cristian";
saludar();
user = "Sol";
saludar();
saludar();

------------------------------
cbonomo@usuario-ThinkPad-X1-Carbon-Gen-8:~/Javascript/01-fundamentos$ node console.js 
Cristian: 1
Sol: 1
Sol: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  countReset()
&lt;/h2&gt;

&lt;p&gt;con countReset() podemos resetear la cuenta de count()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let user = "";

function saludar() {
    console.count(user);
    return "hola " + user;
}

user = "Cristian";
saludar();
user = "Sol";
saludar();
saludar();
console.countReset(user); //resetea a 0 el indice Sol
saludar();

------------------------------------------------------
cbonomo@usuario-ThinkPad-X1-Carbon-Gen-8:~/Cursos/Javascript/01-fundamentos$ node console.js 
Cristian: 1
Sol: 1
Sol: 2
Sol: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  debug()
&lt;/h2&gt;

&lt;p&gt;imprime un mensaje en la consola a nivel de debug, el cual sera solo mostrado si la consola esta configurada para mostrar esta salida. En la consola de Google Chrome, por ejemplo, solo se vera si activamos la opcion Verbose en los Default Levels, en Node se muestra por defecto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.debug('Error printing data');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  dir()
&lt;/h2&gt;

&lt;p&gt;Mediante dir(), podemos mostrar una lista interactiva de métodos de un objeto JavaScript. Es un método bastante útil para poder ver los métodos de un objeto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.dir(console);

--------------------------
Object [console] {
  log: [Function: log],
  warn: [Function: warn],
  dir: [Function: dir],
  time: [Function: time],
  timeEnd: [Function: timeEnd],
  timeLog: [Function: timeLog],
  trace: [Function: trace],
  assert: [Function: assert],
  clear: [Function: clear],
  count: [Function: count],
  countReset: [Function: countReset],
  group: [Function: group],
  groupEnd: [Function: groupEnd],
  table: [Function: table],
  debug: [Function: debug],
  info: [Function: info],
  dirxml: [Function: dirxml],
  error: [Function: error],
  groupCollapsed: [Function: groupCollapsed],
  Console: [Function: Console],
  profile: [Function: profile],
  profileEnd: [Function: profileEnd],
  timeStamp: [Function: timeStamp],
  context: [Function: context]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  dirxml()
&lt;/h2&gt;

&lt;p&gt;Muestra la misma información que dir, pero en formato XML.&lt;/p&gt;

&lt;h2&gt;
  
  
  error()
&lt;/h2&gt;

&lt;p&gt;Este método nos permite mostrar un mensaje de error en la consola. A simple vista, puede parecernos igual a console.log(), pero la diferencia es que mientras console.log() escribe mediante stdout, console.error() escribe a stderr, lo que nos permite utilizarlos de manera diferente. Les recomiendo correr este código en Node y en la consola de Chrome para ver la diferencia de manejo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.error('Error reading data');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  group() y groupEnd()
&lt;/h2&gt;

&lt;p&gt;este método nos permite crear indentaciones en nuestros logs, mediante agrupaciones. Utilizaremos group() para abrir un nivel y groupEnd() para cerrarlo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log('Nivel base');
console.group();
console.log('Nivel 1');
console.group();
console.log('Nivel 2');
console.group();
console.log('Nivel 3');
console.groupEnd();
console.log('Nivel 2');
console.groupEnd();
console.log('Nivel 1');
console.groupEnd();

---------------------------
Nivel base
  Nivel 1
    Nivel 2
      Nivel 3
    Nivel 2
  Nivel 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  groupCollapsed()
&lt;/h2&gt;

&lt;p&gt;Este método nos permite crear un grupo desplegable, el cual al imprimirse por consola nos permitirá abrirlo y cerrarlo. Recomiendo probar esta funcionalidad en la consola del navegador.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.log('Nivel base');
console.groupCollapsed('Ver más');
console.log('Nivel 1');
console.group();
console.log('Nivel 2');
console.group();
console.log('Nivel 3');
console.groupEnd();
console.log('Nivel 2');
console.groupEnd();
console.log('Nivel 1');
console.groupEnd();

-----------------------------
Nivel base
VM64:2 Ver más
VM64:3 Nivel 1
VM64:4 console.group
VM64:5 Nivel 2
VM64:6 console.group
VM64:7 Nivel 3
VM64:9 Nivel 2
VM64:11 Nivel 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  info()
&lt;/h2&gt;

&lt;p&gt;Este método muestra como salida un mensaje de información a la consola. Normalmente, aparece un icono con una 'i' para indicar esto.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.info('Este es un mensaje informativo');
VM154:1 Este es un mensaje informativo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  log()
&lt;/h2&gt;

&lt;p&gt;Como hablamos al comienzo, uno de los primeros métodos que aprendemos. Veamos algunas funciones extra que tiene.&lt;/p&gt;

&lt;p&gt;Podemos utilizar sustituciones dentro del string, para imprimir determinados tipos de valores.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let celular = {
    codArea: 54,
    prefijo: 11,
    numero: 12345687
};

let duracion = 5.6;

for (let i = 0; i &amp;lt; 5; i++) {

    console.log("Hola, %s, este es el mensaje número %d al teléfono %o, con una duración de %f segundos", "Cristian",i+1, celular, duracion);

}
--------------------------------------
Hola, Cristian, este es el mensaje número 1 al teléfono { codArea: 54, prefijo: 11, numero: 12345687 }, con una duración de 5.6 segundos
Hola, Cristian, este es el mensaje número 2 al teléfono { codArea: 54, prefijo: 11, numero: 12345687 }, con una duración de 5.6 segundos
Hola, Cristian, este es el mensaje número 3 al teléfono { codArea: 54, prefijo: 11, numero: 12345687 }, con una duración de 5.6 segundos
Hola, Cristian, este es el mensaje número 4 al teléfono { codArea: 54, prefijo: 11, numero: 12345687 }, con una duración de 5.6 segundos
Hola, Cristian, este es el mensaje número 5 al teléfono { codArea: 54, prefijo: 11, numero: 12345687 }, con una duración de 5.6 segundos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otra funcionalidad interesante es dar estilos a nuestros mensajes. Podemos aplicar estilos a nuestra salida de consola para que sea mas atractiva, o según lo que deseemos resaltar.&lt;br&gt;
Tenemos dos formas de realizar esto según donde mostraremos nuestro mensaje.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//navegador
console.log("%cError detectado","color: yellow; font-style: italic; background-color: black");

//node
console.log('\x1b[31m%s\x1b[0m', 'Error detectado');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nota: en el caso de Node, existen paquetes como &lt;a href="https://www.npmjs.com/package/colors" rel="noopener noreferrer"&gt;colors&lt;/a&gt; para simplificarnos la tarea.&lt;/p&gt;

&lt;h2&gt;
  
  
  table()
&lt;/h2&gt;

&lt;p&gt;este método nos permite imprimir una tabla con valores por consola. Debemos pasarle por argumento un array o un objeto&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const persona = {
    nombre: 'Cristian',
    apellido: 'Bonomo'
};

console.table(persona);

const lenguajes = ['Javascript','PHP','Java','Python'];

console.table(lenguajes);

----------------------------------
┌──────────┬────────────┐
│ (index)  │   Values   │
├──────────┼────────────┤
│  nombre  │ 'Cristian' │
│ apellido │  'Bonomo'  │
└──────────┴────────────┘
┌─────────┬──────────────┐
│ (index) │    Values    │
├─────────┼──────────────┤
│    0    │ 'Javascript' │
│    1    │    'PHP'     │
│    2    │    'Java'    │
│    3    │   'Python'   │
└─────────┴──────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  time()
&lt;/h2&gt;

&lt;p&gt;Este método nos permite registrar cuanto tiempo puede tomar una operación en específico. Vamos a utilizarlo en conjunto con timeLog(), el cual nos permite ver el valor actual de un timer previamente inicializado, y timeEnd(), el cual frena el timer.&lt;br&gt;
Para este caso, voy a simular una función init(), que solo realizara un conteo, pero también podría ser una métrica de cuanto tiempo toma el sistema para inicializar, o responder a una petición.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function init() {
    let count = 0;
    for (let i = 0; i &amp;lt; 100; i++) {
        count++;
    }
}

console.time('init');
init();
console.timeLog('init');
init();
console.timeEnd('init');

--------------------------------------
init: 0.092ms
init: 0.276ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  trace()
&lt;/h2&gt;

&lt;p&gt;Este método nos permite realizar un trace sobre las funciones llamadas hasta el punto en que llamamos a trace()&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function funcionUno() {
    function funcionDos() {
        console.trace();
    }
    funcionDos();
}

funcionUno();

Trace
    at funcionDos (/home/cbonomo/Javascript/01-fundamentos/console.js:133:17)
    at funcionUno (/home/cbonomo/Javascript/01-fundamentos/console.js:135:5)
    at Object.&amp;lt;anonymous&amp;gt; (/home/cbonomo/Cursos/Javascript/01-fundamentos/console.js:138:1)
    at Module._compile (node:internal/modules/cjs/loader:1095:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1147:10)
    at Module.load (node:internal/modules/cjs/loader:975:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  warn()
&lt;/h2&gt;

&lt;p&gt;Este método nos permite mostrar un mensaje de alerta en la consola web. En esta consola, nos mostrará el mensaje junto con el símbolo amarillo de warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;console.warn('Este es un mensaje de alerta');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Así, llegamos al final de este repaso por los métodos que nos ofrece la clase console(), espero les sea de utilidad a la hora de programar, para mostrar mejores mensajes por consola y poder implementar mas fácilmente las soluciones que necesiten en su desarrollo.&lt;/p&gt;

&lt;p&gt;Hasta la próxima!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
      <category>spanish</category>
    </item>
  </channel>
</rss>
